diff --git a/.github/ISSUE_TEMPLATE/--------.md b/.github/ISSUE_TEMPLATE/--------.md index 26076cfc..cf238abd 100644 --- a/.github/ISSUE_TEMPLATE/--------.md +++ b/.github/ISSUE_TEMPLATE/--------.md @@ -23,7 +23,7 @@ assignees: Johnserf-Seed **桌面(请填写以下信息):** -操作系统:[例如windows10 64bit] -vpn代理:[例如开启、关闭] --项目版本:[如1.4.2.2] +-项目版本:[如1.5.0.0] -py版本:[如3.11.1] -依赖库的版本:[出错的库版本号] diff --git a/.gitignore b/.gitignore index b617f89a..8a440f27 100644 --- a/.gitignore +++ b/.gitignore @@ -137,6 +137,8 @@ dmypy.json /Download/ readconf.py .vscode -/Util/algorithm/node_modules/ +/Server/node_modules/ 1.py -test.py \ No newline at end of file +test.py +/refactor +*.db \ No newline at end of file diff --git a/API/API.js b/API/API.js deleted file mode 100644 index bce81d82..00000000 --- a/API/API.js +++ /dev/null @@ -1,2090 +0,0 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ -/******/ return result; -/******/ } -/******/ -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 30: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // create a fake namespace object -/******/ // mode & 1: value is a module id, require it -/******/ // mode & 2: merge all properties of value into the ns -/******/ // mode & 4: return value when already ns object -/******/ // mode & 8|1: behave like require -/******/ __webpack_require__.t = function(value, mode) { -/******/ if(mode & 1) value = __webpack_require__(value); -/******/ if(mode & 8) return value; -/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; -/******/ var ns = Object.create(null); -/******/ __webpack_require__.r(ns); -/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); -/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); -/******/ return ns; -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = "https://sf3-scmcdn2-tos.pstatp.com/ies/fe_app_new/"; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // add entry module to deferred list -/******/ deferredModules.push([29,0]); -/******/ // run deferred modules when ready -/******/ return checkDeferredModules(); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "113c86d39c3ccfcccc8a": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -__webpack_require__("46107d86b7941adbc814"); - -module.exports = { - show: function show() { - $('#pagelet-loading').show(); - }, - hide: function hide() { - $('#pagelet-loading').hide(); - } -}; - -/***/ }), - -/***/ "12a5c4dc26e1824fdd8b": -/***/ (function(module, exports, __webpack_require__) { - -// extracted by mini-css-extract-plugin -module.exports = {"pagelet-banner":"pagelet-banner","hide":"hide","move-hide":"move-hide","app-download":"app-download","banner-show":"banner-show","up-down":"up-down","banner-show1":"banner-show1","download-btn":"download-btn","txt":"txt"}; - -/***/ }), - -/***/ "166003ecd7694b2377b6": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var OpenBrowser = __webpack_require__("adf14ea98b2960ef189a"); - -var isVoice = false; -var modal = new OpenBrowser(); // jump2app相关 - -var schemas = { - home: 'snssdk1128://feed?refer=web&gd_label={{gd_label}}', - // 首页feed - detail: 'snssdk1128://aweme/detail/{{id}}?refer=web&gd_label={{gd_label}}&appParam={{appParam}}&needlaunchlog=1', - // 作品详情页 - user: 'snssdk1128://user/profile/{{uid}}?refer=web&gd_label={{gd_label}}&type={{type}}&needlaunchlog=1', - // 用户主页 - challenge: 'snssdk1128://challenge/detail/{{id}}?refer=web&is_commerce=0', - // 挑战详情 - music: 'snssdk1128://music/detail/{{id}}?refer=web', - // 音乐详情 - live: 'snssdk1128://live?room_id={{room_id}}&user_id={{user_id}}&u_code={{u_code}}&from=webview&refer=web', - // 直播间 - webview: 'snssdk1128://webview?url={{url}}&from=webview&refer=web', - // webview - webview_fullscreen: 'snssdk1128://webview?url={{url}}&from=webview&hide_nav_bar=1&refer=web', - // webview 沉浸式 - poidetail: 'snssdk1128://poi/detail?id={{id}}&from=webview&refer=web', - // poi详情页 - forward: 'snssdk1128://forward/detail/{{id}}', - // 转发详情页 - billboard_word: 'snssdk1128://search/trending', - // 热搜词榜 - billboard_video: 'snssdk1128://search/trending?type=1', - // 热搜视频榜 - billboard_music: 'snssdk1128://search/trending?type=2', - // 热搜音乐榜 - billboard_positive: 'snssdk1128://search/trending?type=3', - // 正能量榜 - billboard_star: 'snssdk1128://search/trending?type=4' // 明星榜 - -}; // universal link url - -var UNIVERSAL_LINK = 'https://www.amemv.com/redirect/?redirect_url='; // 小型模板引擎nano,仅支持JSON数据 - -function nano(template, data) { - return template.replace(/\{\{([\w\.]*)\}\}/g, function (str, key) { - var keys = key.split('.'); - var v = data[keys.shift()]; - - for (var i = 0, l = keys.length; i < l; i++) { - v = v[keys[i]]; - } - - return typeof v !== 'undefined' && v !== null ? v : ''; - }); -} // 工具函数结束 -// 拼接schema - - -function _schemaFactory(type, params) { - var schema = schemas[type] || null; - - if (getQueryString('app') == 'douyin_lite') { - // 抖音极速版替换appid - schema = schema.replace(/^snssdk1128/, 'snssdk2329'); - } - - if (schema) { - schema = nano(schema, params || {}); - } - - return schema; -} - -function _nativeLinkFactory(type, params) { - var link_tmpl = {}; - var link = link_tmpl[type] || null; - - if (link) { - link = nano(link, params); - } - - return link; -} - -function _openAppInIframe(schema) { - var a = document.createElement('a'); - a.setAttribute('href', schema); - a.click(); - $('body').append("")); -} - -function _getDeepLink(schema, downloadLink) { - var deepLink = ''; // 对于universal link,ios内部schema是aweme开头,所提替换链接上的schema为 aweme 开头 - - if (getQueryString('app') == 'douyin_lite') { - // 抖音极速版替换appid - schema = schema.replace(/^snssdk1128/, 'snssdk2329'); - return schema; - } - - schema = schema.replace(/^snssdk1128/, 'aweme'); - deepLink = UNIVERSAL_LINK + encodeURIComponent(schema); - - if (downloadLink) { - if (downloadLink.startsWith('//')) { - downloadLink = 'https:' + downloadLink; - } - - deepLink += "&next_url=".concat(encodeURIComponent(downloadLink)); - } - - return deepLink; -} -/** - * @params {String} type 跳转类型 - * @params {Object} params 跳转参数 - * @params {String} downloadLink 下载地址 - */ - - -function jumpToNativeApp(type, params, downloadLink) { - var schema = _schemaFactory(type || 'home', params); - - if (!schema) { - return; - } // alert(schema); - - - if ($.os.ios) { - var currentUrl = location.href; - var version = parseFloat($.os.version); - var ios9 = parseInt(version, 10) >= 9; // ios9 以下,直接用iframe的方式 - - if (!ios9) { - _openAppInIframe(schema); - - return; - } - /** - * 如果是ios9及以上,那么尝试用deeplink方式打开 - * 否则,仍然像目前这样的调用方式 - * IOS下的qqbrowser,因为跳转会白屏,不采用universal link方式 - */ - - - if (ios9 && !$.browser.qqbrowser) { - var deepLink = _getDeepLink(schema, downloadLink); - - if (schema.indexOf('detail') >= 0) { - copyHandle(); - } - - if (isVoice) { - top.location.href = deepLink; - } else { - location.href = deepLink; - } // safari里,服务端重定向到schema后,无法打开app,会弹出弹框,通过刷新刷掉弹框 - // if (!$.browser.weixin && $.browser.safari) { - // setTimeout(function() { - // alert('his' + location.href) - // location.href = deepLink; - // }, 100); - // } - - - return; - } - /** - * ios9 qq浏览器 - * ios9 上,通过schema跳转应用,如果安装了会提示是否跳转app,如果没安装,会提示无法打开url - */ - - - setTimeout(function () { - location.href = schema; - setTimeout(function () { - if (document.hidden || document.webkitHidden) { - location.href = currentUrl; - } - }, 1300); - }, 10); - } else { - if (!$.browser.weixin) { - _openAppInIframe(schema); - } - } -} - -function apploadHandler(opts) { - var schema = _schemaFactory(opts.type || 'home', opts.params); - - var self = this; - - if ($.browser.weixin) { - var schemaName = function (sys) { - var suffix = '_scheme'; - var name = sys.ios ? 'ios' : 'android'; - return name + suffix; - }($.os); // var schema = _schemaFactory(opts.type, opts.params); - - - if (schema) { - var url = [opts.downloadLink, opts.downloadLink.indexOf('?') > -1 ? '&' : '?', schemaName, "=".concat(encodeURIComponent(schema))].join(''); // ios微信直接加修改location会被屏蔽,原因未明 - - setTimeout(function () { - location.href = url; - }); - return; - } - } - - if ($.os.ios) { - var osVersion = parseFloat($.os.version); - var ios9 = $.os.ios && osVersion >= 9; - /* - safari中,下载短链会呼出apple store, wap2app的时候如果用户没有安装,会alert一个错误,导致下载短链无法执行, - 因此必须先执行下载 - */ - - if (!$.browser.weixin && $.browser.safari && ios9) { - // _gotoAppDownload(opts); - // 好象不用唤起,直接下载就行啦,不敢改 :( - // setTimeout(function () { - // _wap2app(opts); - // }, 1000); - jumpToNativeApp(opts.type, opts.params, opts.downloadLink); - - if ($.browser.qq) { - modal.open(); - } - } else { - /* ios - 微信中,下载短链302到应用宝,同时universal link会302到 snssdk143://xx,而微信会屏蔽snssdk143://这种非http协议, - 由于两者时间很短,几乎同时,似乎对短链的302也屏蔽的,(不太了解机制),所以两者时间上要有一定的间隔。 - 由于下载短链在当前页面打开应用宝页面,就不会执行后面的代码,所以先进行跳转 - */ - _wap2app(opts); - - setTimeout(function () { - _checkOpen(function (isOpen) { - !isOpen && _gotoAppDownload(opts); - }); - }, 1000); - } - - return; - } else { - // android 其它 - _openAppInIframe(schema); - - _checkOpen(function (isOpen) { - !isOpen && _gotoAppDownload(opts); - }); // qq空间无法唤起(下载)app, qq会话窗口可以,但是无法区别是从qq会话还是空间打开的页面 - // 所以在qq内,使用一个弹层引导用户在其它浏览器打开页面 - - - if ($.browser.qq) { - modal.open(); - } - } -} - -function _wap2app(opts) { - jumpToNativeApp(opts.type, opts.params); -} - -function _gotoAppDownload(opts) { - if (opts.downloadLink) { - if (isVoice) { - top.location.href = opts.downloadLink; - } else { - location.href = opts.downloadLink; - } - - location.href = opts.downloadLink; - } -} // 检查app是否打开 - - -function _checkOpen(cb) { - var _count = 0; - - var _clickTime = Number(new Date()); - - var intHandle; - - function check(elsTime) { - if (isVoice) { - if (elsTime > 1000 || top.document.hidden || top.document.webkitHidden) { - cb(true); - } else { - cb(false); - } - } else { - if (elsTime > 1000 || document.hidden || document.webkitHidden) { - cb(true); - } else { - cb(false); - } - } - } // 启动间隔20ms运行的定时器,并检测累计消耗时间是否超过1000ms,超过则结束 - - - intHandle = setInterval(function () { - _count++; - - var elsTime = Number(new Date()) - _clickTime; - - if (_count >= 15 || elsTime > 1000) { - clearInterval(intHandle); - check(elsTime); - } - }, 40); -} // gd_label是唤起应用时,通知客户端记录打点的字段 -// gd_label参数: https://wiki.bytedance.net/pages/viewpage.action?pageId=179404954 -// gd_label的值有哪些: https://docs.google.com/spreadsheets/d/1EWzh4gIbE861d9Rbk-M7QNYCPnltkNt9r2ETVSV2wf4/edit#gid=2126697475 - - -function setGdLabel(val) { - // click_schema_ugdsp_aweme: DSP拉活(https://bytedance.feishu.cn/docs/doccnZNSYA9Zc3FMeGQ490l6dZf#) - var scene = getQueryString('scene'); - - if (scene == 'dsp') { - return 'click_schema_ugdsp_aweme'; - } - - if (/^click_(wexin|wap)_/.test(val)) { - return val; - } else { - var type = function (weixin) { - return weixin ? 'weixin' : 'wap'; - }($.browser.weixin); - - return "click_".concat(type, "_").concat(val); - } -} - -function getQueryString(name) { - var reg = new RegExp("(^|&)".concat(name, "=([^&]*)(&|$)"), 'i'); - var r = window.location.search.substr(1).match(reg); - - if (r != null) { - return unescape(r[2]); - } - - return null; -} - -function getUrlFromDl(dl, opts) { - var url = ''; - - try { - var dlFromQuery = getQueryString('dl'); - url = dlFromQuery && opts.useDl && "//d.douyin.com/".concat(dlFromQuery, "/") || ''; - var query = (dl || '').split('?')[1] || ''; - - if (url && query) { - url = "".concat(url, "?").concat(query); - } - } catch (e) { - console.log(e); - } - - return url; -} - -function copyHandle() { - var input = document.createElement('input'); - document.body.appendChild(input); - input.setAttribute('readonly', 'readonly'); - input.setAttribute('value', window.location.href.replace('aweme.snssdk.com', 'www.iesdouyin.com')); - input.select(); - input.setSelectionRange(0, 9999); - - if (document.execCommand('copy')) { - document.execCommand('copy'); - } - - document.body.removeChild(input); - console.log('copy'); -} // download_sdk相关 - -/** - * @功能:跳转下载app,并尝试唤起app - * @dependance:window.jumpToNativeApp - */ - - -module.exports = { - downloadApp: function downloadApp(dl, opts, voice) { - isVoice = voice; - /** - /* downloadLink: '' // 跳转链接 - * type: '', // app唤起类型,参考schemas中的key - * params: {}, // 跳转app参数,替换到对应schema中 - */ - - if (opts.params && opts.params.gd_label) { - var gd_label = setGdLabel(opts.params.gd_label); - var params = $.extend({}, opts.params, { - gd_label: gd_label - }); - opts.params = params; - } - - apploadHandler($.extend({ - downloadLink: getUrlFromDl(dl, opts) || dl - }, opts)); - } -}; - -/***/ }), - -/***/ "1d2f6dd2d416eba8db8a": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = { - getQueryStr: function getQueryStr(params) { - return Object.keys(params).map(function (key) { - return [key, encodeURIComponent(params[key])].join('='); - }).join('&'); - }, - getQueryObj: function getQueryObj() { - var queryStr = location.search.slice(1); - - if (!queryStr) { - return {}; - } - - var aTemp = queryStr.split('&'); - var obj = {}; - aTemp.forEach(function (param) { - var arr = param.split('='); - obj[arr[0]] = arr[1]; - }); - return obj; - }, - getLastPathId: function getLastPathId() { - return location.pathname.match(/\d+/g)[0]; - } -}; - -/***/ }), - -/***/ "206d7d768e507237f87a": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var appTrack = { - getParams: function getParams(params, url) { - return { - extra: JSON.stringify(params), - // 用于短链匹配 - append: JSON.stringify({ - // 用于动态打包 - openurl: url || '', - postdata: [params] - }) - }; - } -}; -module.exports = appTrack; - -/***/ }), - -/***/ 29: -/***/ (function(module, exports, __webpack_require__) { - -module.exports = __webpack_require__("78a3850f5800635eb493"); - - -/***/ }), - -/***/ "3e4859239cf0f183ef63": -/***/ (function(module, exports) { - -module.exports = function(obj) { -obj || (obj = {}); -var __t, __p = ''; -with (obj) { -__p += '
' + -((__t = ( text )) == null ? '' : __t) + -'
\n'; - -} -return __p -} - -/***/ }), - -/***/ "46107d86b7941adbc814": -/***/ (function(module, exports, __webpack_require__) { - -// extracted by mini-css-extract-plugin -module.exports = {"pagelet-loading":"pagelet-loading","icon":"icon","loading":"loading","txt":"txt"}; - -/***/ }), - -/***/ "492a4f83e13722f2250e": -/***/ (function(module, exports) { - -module.exports = function(obj) { -obj || (obj = {}); -var __t, __p = '', __j = Array.prototype.join; -function print() { __p += __j.call(arguments, '') } -with (obj) { - - _.each(list, function (item, index) { ; -__p += '\n
  • \n
    \n
    \n \n \n
    \n
    \n

    ' + -((__t = ( item.name )) == null ? '' : __t) + -'

    \n

    ' + -((__t = ( item.count )) == null ? '' : __t) + -' 人使用

    \n

    ' + -((__t = ( item.duration )) == null ? '' : __t) + -'

    \n
    \n
    \n
  • \n'; - }) ; - - -} -return __p -} - -/***/ }), - -/***/ "52100986343de6f77ea3": -/***/ (function(module, exports, __webpack_require__) { - -// extracted by mini-css-extract-plugin - -/***/ }), - -/***/ "5eac54fb4462091116c6": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * @desc 四舍五入到小数点后 digit 位 - */ -function round(number, digit) { - digit = digit || 1; - return Math.round(number * Math.pow(10, digit)) / Math.pow(10, digit); -} -/** - * @desc 大于10000的数转化为单位为W的数,精确到小数点后1位 - */ - - -function getShowNum(number) { - if (number < 9999) { - return number; - } - - var showNum = round(number / 10000, 1); - return "".concat(showNum, "w"); -} -/** - * @desc 数字补0 - */ - - -function pad(num, size) { - return "000000000".concat(num).substr(-size); -} -/** - * @desc 将秒转成 分:秒 - * @example 70 => 01:10 - */ - - -function formatSecond(second) { - var m = pad(~~(second / 60), 2); - var s = pad(second % 60, 2); - return [m, s].join(':'); -} -/** - * @desc 生成 [m, n] 的随机数 - */ - - -function random(min, max) { - return min + Math.floor((max - min + 1) * Math.random()); -} - -module.exports = { - round: round, - formatSecond: formatSecond, - getShowNum: getShowNum, - random: random -}; - -/***/ }), - -/***/ "5f2f9bfeb1d577266d34": -/***/ (function(module, exports) { - -module.exports = function(obj) { -obj || (obj = {}); -var __t, __p = '', __j = Array.prototype.join; -function print() { __p += __j.call(arguments, '') } -with (obj) { - - _.each(list, function (item) { ; -__p += '\n\n
  • \n\n '; - if(item.isImage) { ; -__p += '\n
    \n \n\n '; - if (item.good_choice === 1) { ; -__p += '\n
    \n '; - } ; -__p += '\n
    \n \n ' + -((__t = ( item.digg_num )) == null ? '' : __t) + -'\n
    \n
    \n '; - } else { ; -__p += '\n
    \n '; - if (item.good_choice === 1) { ; -__p += '\n
    \n '; - } ; -__p += '\n
    \n \n ' + -((__t = ( item.digg_num )) == null ? '' : __t) + -'\n
    \n
    \n '; - } ; -__p += '\n\n
  • \n\n'; - }) ; - - -} -return __p -} - -/***/ }), - -/***/ "68b15f4b6164c718c4af": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -// 本地存储 -module.exports = { - getLocal: function getLocal(key) { - return window.localStorage ? window.localStorage.getItem(key) : null; - }, - setLocal: function setLocal(key, value, callback) { - try { - window.localStorage && window.localStorage.setItem(key, value); - } catch (e) { - callback && callback(); - } - }, - length: function length() { - return window.localStorage && window.localStorage.length; - }, - clearLocal: function clearLocal() { - return window.localStorage ? window.localStorage.clear() : null; - }, - removeLocal: function removeLocal(key) { - window.localStorage && window.localStorage.removeItem(key); - }, - getSession: function getSession(key) { - return window.sessionStorage ? window.sessionStorage.getItem(key) : null; - }, - setSession: function setSession(key, value, callback) { - try { - window.sessionStorage && window.sessionStorage.setItem(key, value); - } catch (e) { - callback && callback(); - } - }, - removeSession: function removeSession(key) { - window.sessionStorage && window.sessionStorage.removeItem(key); - } -}; - -/***/ }), - -/***/ "6c84fa149b373f4029bb": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _bytedDpEventCollector = __webpack_require__("682c223c8090c9c699fb"); - -var _bytedDpEventCollector2 = _interopRequireDefault(_bytedDpEventCollector); - -var _utils = __webpack_require__("e844fe34c02b3cce5ed3"); - -var _utils2 = _interopRequireDefault(_utils); - -var _qs = __webpack_require__("a63b0d047588ea783f61"); - -var _qs2 = _interopRequireDefault(_qs); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -var TeaLogger = window.collectEvent || _bytedDpEventCollector2.default; -var appId = 1243; -var appName = 'douyin_reflow'; // 可选择开启debug模式。debug模式上报地址为测试服务器(只支持http。测试https需关闭debug模式,上报到线上地址)。 -// collectEvent.setDebug(true); -// 设置appId。必须配置。 - -TeaLogger.setAppId(appId); // 设置一些必备的字段。sdk会延时等到这些字段都赋值完毕后,才发送收集的事件。 -// 其中(user_unique_id、app_id)为必须设置的字段,否则服务器会返回失败码。 -// 背景:因为一些字段是异步获取的,所以可能在发送事件之后才赋值。 - -TeaLogger.setRequiredKeys({ - user: ['user_unique_id'], - header: ['app_id'] // eventCommonParams: ['paramCommon'], - // customHeaders: ['flow'] - -}); // 设置header相关字段 -// headers比较特殊。headers里的key,后续会在处理的时候拿出来与app_id、headers并列。 - -TeaLogger.setHeader({ - app_id: appId, - app_name: appName, - os_name: $.os.android ? 'android' : $.os.ios ? 'ios' : 'unknown', - os_version: "".concat($.os.version), - traffic_type: $.browser.weixin ? 'weixin' : 'wap' -}); // 设置通用字段,增加自定义头 - -TeaLogger.setHeaderHeaders({ - // url相关 - href: location.href, - host: location.host, - pathname: location.pathname, - protocol: location.protocol, - // 手机信息 - user_agent: navigator.userAgent, - screen_resolution: "".concat(window.screen.width, "*").concat(window.screen.height) -}); - -function pick(obj, keys) { - return keys.map(function (k) { - return k in obj ? _defineProperty({}, k, obj[k]) : {}; - }).reduce(function (res, o) { - return $.extend(res, o); - }, {}); -} // 从url获取tea打点必要参数,用于pv、tap事件的上报 - - -var list = ['utm_source', 'utm_medium', 'utm_campaign']; - -var queryObj = _qs2.default.parse(location.href); - -var taeCommonParams = pick(queryObj, list); // 设置通用字段,追加到每次的请求的params里 - -TeaLogger.setEventCommonParams($.extend({ - page_url: window.location.href -}, taeCommonParams)); // 设用户相关信息 - -TeaLogger.setUser({ - user_unique_id: _utils2.default.getTTWebId() -}); -TeaLogger.setIntranetMode(false); -exports.default = TeaLogger; - -/***/ }), - -/***/ "78a3850f5800635eb493": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _utils = __webpack_require__("e844fe34c02b3cce5ed3"); - -var _utils2 = _interopRequireDefault(_utils); - -var _tea = __webpack_require__("6c84fa149b373f4029bb"); - -var _tea2 = _interopRequireDefault(_tea); - -var _bytedAcrawler = __webpack_require__("9bd2804c7e68ac461d65"); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/** - * @desc 个人主页回流页 - */ -__webpack_require__("52100986343de6f77ea3"); - -__webpack_require__("a62155a7eb13f352b7e3"); - -var LIST_TMPL = __webpack_require__("5f2f9bfeb1d577266d34"); - -var MUSIC_TMPL = __webpack_require__("492a4f83e13722f2250e"); - -var MAIN_TMPL = __webpack_require__("f1e83373459b7c5ed036"); - -var EMPTY_TMPL = __webpack_require__("3e4859239cf0f183ef63"); - -var number = __webpack_require__("5eac54fb4462091116c6"); - -var urlUtil = __webpack_require__("1d2f6dd2d416eba8db8a"); - -var storage = __webpack_require__("68b15f4b6164c718c4af"); - -var appTrack = __webpack_require__("206d7d768e507237f87a"); - -var downloadAction = __webpack_require__("166003ecd7694b2377b6"); - -var banner = __webpack_require__("d712b069d922fb6d94e3"); - -var loading = __webpack_require__("113c86d39c3ccfcccc8a"); - -var tips = __webpack_require__("9740e2f820a158083b3c"); - -var isTypeSMS = false; -var COUNT = 21; // 每屏21个 - -var DOWNLOAD_CLICK_LIMIT = 0; // 点击第n次下载 不允许互相跳转 - -var nonce = ''; // nonce设置 作用参见readme@注释1 - -var signature = ''; // signature设置 作用参见readme@注释1 - -var isLoading = false, - loadEnd = false, - hasListData = { - 'post': false, - 'favorite': false -}, - params = { - user_id: '', - sec_uid: '', - count: COUNT, - max_cursor: 0, - aid: 1128 -}; -var DOWNLOAD_URL_SETTING = { - 'reflow-user': '//d.douyin.com/PxRW/', - 'sms-invite': '//d.douyin.com/fMcS/' -}; -var EMPTY_TEXT = { - 'post': 'TA还没有发布过作品', - 'favorite': 'TA还没有喜欢的作品' -}; -var pageletWorklist = $('#pagelet-worklist'); -var dytk = ''; // 主要是用于控制开始放大的位置,从个人profile回流页添加图文时添加的 - -var rectObject = null; -$(function () { - window.Raven && window.Raven.config('//key@m.toutiao.com/log/sentry/v2/174', { - whitelistUrls: [/bytecdn\.cn/], - shouldSendCallback: function shouldSendCallback(data) { - return true; - }, - tags: { - bid: 'douyin_wap', - pid: 'reflow_video' - } - }).install(); - var userId = urlUtil.getLastPathId(); - init({ - uid: userId - }); -}); // 下载app - -function downloadApp(downloadLink, opts) { - var schema = 'aweme://user/profile/' + opts.params.uid; - var appTrackParams = appTrack.getParams({ - reflow_page_uid: opts.params.uid, - __type__: "app_track" - }, schema); - var url = downloadLink + '?' + urlUtil.getQueryStr(appTrackParams); - opts.useDl = true; - downloadAction.downloadApp(url, opts); -} - -function getQueryString(name) { - var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); - var r = window.location.search.substr(1).match(reg); - - if (r != null) { - return unescape(r[2]); - } - - return null; -} - -function bind(config) { - var _config = config; - - _utils2.default.initScrollEvents(50, 100); - - $(window).on("scrollEnd", function () { - getWorkList(listType); - }); - $(".get-list").on("click", function (e) { - var listType = $(e.currentTarget).data('type'); - - if ($(e.currentTarget).hasClass('active') === true) { - return; - } else { - $(e.currentTarget).addClass('active').siblings('.tab').removeClass('active'); - } - - reload(); - isLoading = false; - loadEnd = false; - params.max_cursor = 0; - getWorkList(listType); - }); - $(".go-author").on("click", function () { - if (isTypeSMS) { - goUser(config.uid); - return; - } - - downloadApp(DOWNLOAD_URL_SETTING['reflow-user'], { - type: 'user', - params: { - uid: config.uid, - type: 'need_follow', - gd_label: 'profile_follow' - } - }); - }); - pageletWorklist.on('click', '.music-item', function (e) { - var mid = $(e.currentTarget).attr('data-id'); // 数据打点 - - _tea2.default.send('tap', { - type: 'feature', - target: "work_list" - }); // 控制观看次数,如果超过次数会进行下载 - - - var count = storage.getSession('com.aweme.reflow-music-count') || 1; - - if (+count >= 3) { - //点击第n次直接下载 - _tea2.default.send('tap', { - type: 'download_feature', - target: "work_list" - }); - - downloadApp(DOWNLOAD_URL_SETTING['reflow-user'], { - type: 'music', - params: { - id: mid, - gd_label: 'profile_feature' - } - }); - return; - } - - storage.setSession('com.aweme.reflow-music-count', ++count); // 跳转原创音乐回流 - - location.href = '/share/music/' + mid; - }); - pageletWorklist.on('click', '.goWork', function (e) { - var config = this.config; - var id = $(e.currentTarget).attr('data-id'); - var mediaType = $(e.currentTarget).attr('data-type'); - var imgSrc = $(e.currentTarget).attr('data-url'); - - _tea2.default.send('tap', { - type: 'feature', - target: "work_list" - }); // 通讯录需求 - - - if (isTypeSMS) { - goUser(_config.uid); - return; - } // 如果是图片,就进行缩放 - - - if (mediaType === 'image') { - scaleImage(e.currentTarget, imgSrc); - return; - } // 控制观看次数,如果超过次数会进行下载 - - - var count = storage.getSession('com.aweme.reflow-user-count') || 1; - - if (+count >= DOWNLOAD_CLICK_LIMIT) { - //点击第n次直接下载 - _tea2.default.send('tap', { - type: 'download_feature', - target: "work_list" - }); - - downloadApp(DOWNLOAD_URL_SETTING['reflow-user'], { - type: 'detail', - params: { - id: id, - gd_label: 'profile_feature' - } - }); - return; - } - - storage.setSession('com.aweme.reflow-user-count', ++count); // 跳转视频回流 - - location.href = '/share/video/' + id; - }); // 从个人profile回流页添加图文时开始有的代码 - - $('.close').click(function (e) { - $('#scaleImageWrapper').removeClass('full-screen'); - $('#scaleImageWrapper .enlarge-wrapper').css({ - top: rectObject.top, - left: rectObject.left - }); - setTimeout(function () { - $('#scaleImageWrapper').hide(); - }, 300); - }); // 从个人profile回流页添加图文时开始有的代码,解决滚动穿透的问题 - - $('#scaleImageWrapper').on('touchmove', function (e) { - e.preventDefault(); - }); -} // 从个人profile回流页添加图文时开始有的代码 - - -function scaleImage(ele, imgSrc) { - rectObject = ele.getBoundingClientRect(); - var scaleImageWrapper = $('#scaleImageWrapper')[0]; - $('#scaleImageWrapper .enlarge-wrapper').css({ - "background-image": "url(" + imgSrc + ")", - top: rectObject.top, - left: rectObject.left - }); - $(scaleImageWrapper).show(); - setTimeout(function () { - $(scaleImageWrapper).addClass('full-screen'); - $('#scaleImageWrapper .enlarge-wrapper').css({ - top: 0, - left: 0 - }); - }, 0); -} - -function format(count) { - try { - count = Number(count); - - if (count < 10000) { - return count; - } - - return count / 10000 + 'w'; - } catch (e) { - return count; - } -} - -function getWorkList(type) { - var listType = type; - - if (isLoading || loadEnd) { - return; - } - - isLoading = true; - loading.show(); - var data = $.extend({}, params); - var url = '/web/api/v2/aweme/' + type + '/'; - - if (type === 'music') { - url = '/web/api/v2/music/list/original/'; - var musicParams = { - user_id: params.user_id, - sec_uid: params.sec_uid, - count: params.count, - cursor: params.max_cursor - }; - data = musicParams; - } - - data['_signature'] = signature; // 作用参见readme@注释1 - - data['dytk'] = dytk; - $.ajax({ - url: url, - type: 'get', - dataType: 'json', - data: data, - success: function success(res) { - if (res.status_code !== 0) { - loading.hide(); - tips.show('加载列表失败,请刷新重试'); - return; - } - - params.max_cursor = res.max_cursor || res.cursor; // tips: 采用统一标识 loadEnd 标示是否加载完成 - - if (!res.has_more) { - loadEnd = true; - loading.hide(); - } - - if (res.music_list && res.music_list.length) { - hasListData[type] = true; - var musicData = res.music_list.map(function (item) { - return { - mid: item.mid, - count: item.use_count_desc, - imgUrl: _utils2.default.getDeepValue(item, 'cover_thumb.url_list[0]') || '', - name: item.title, - duration: number.formatSecond(item.duration) - }; - }); - renderWorkList(MUSIC_TMPL, musicData); // params.max_cursor === res.cursor; - } else if (res.aweme_list && res.aweme_list.length) { - hasListData[type] = true; - var awemeList = res.aweme_list.map(function (item) { - return { - id: item.aweme_id, - cover: _utils2.default.getDeepValue(item, 'video.cover.url_list[0]') || '', - good_choice: item.good_choice || 0, - label_url: _utils2.default.getDeepValue(item, 'label_large.url_list[0]') || '', - digg_num: format(_utils2.default.getDeepValue(item, 'statistics.digg_count') || 0), - isImage: item.aweme_type === 2 ? true : false, - imageUrl: _utils2.default.getDeepValue(item, 'image_infos[0].label_large.url_list[0]') - }; - }); - renderWorkList(LIST_TMPL, awemeList); - params.max_cursor === res.max_cursor; - } else { - if (hasListData[type] == false) { - showEmpty({ - type: listType - }); - } - } - }, - error: function error(e) { - console.log('error>', e); - }, - complete: function complete() { - isLoading = false; - } - }); -} - -function showEmpty(params) { - pageletWorklist.show().find('.js-list').html(EMPTY_TMPL({ - text: EMPTY_TEXT[params.type] - })); -} - -function reload() { - pageletWorklist.show().find('.js-list').empty(); -} - -function renderWorkList(template, data) { - pageletWorklist.show().find('.js-list').append(template({ - list: data || [] - })); -} - -function createDOM() { - var code = getQueryString('code'); - - if (code) { - var eleSpan = document.createElement('span'); - eleSpan.className = 'sms-code'; - eleSpan.innerText = code; - eleSpan.id = 'js-copy-text'; - document.body.appendChild(eleSpan); - } -} - -function smsInvite() { - if (window.location.href.indexOf('sms_invite') !== -1) { - isTypeSMS = true; - setTimeout(createDOM, 500); - } -} - -function goUser(uid) { - // 如果是短信邀请链接,点击先复制邀请码 - copyText('js-copy-text'); - downloadApp(DOWNLOAD_URL_SETTING['sms-invite'], { - type: 'user', - params: { - uid: uid, - // 不需要自动关注 - gd_label: 'profile_follow' - } - }); -} - -function copyText(id) { - var eleTarget = document.getElementById(id); - - if (window.getSelection && eleTarget) { - var range = document.createRange(); - range.selectNode(eleTarget); - window.getSelection().removeAllRanges(); - window.getSelection().addRange(range); - return document.execCommand('copy'); - } else if (document.selection) { - var _range = document.body.createTextRange(); - - _range.moveToElementText(eleTarget); - - _range.select().createTextRange(); - - return document.execCommand('copy'); - } -} - -function renderMain(config) { - $.ajax({ - url: '/web/api/v2/user/info/?sec_uid=' + config.sec_uid, - type: 'get', - dataType: 'json', - success: function success(res) { - if (res.status_code !== 0) { - tips.show('加载失败,请刷新重试'); - return; - } - - res.user_info.favoriting_count = format(res.user_info.favoriting_count); - res.user_info.follower_count = format(res.user_info.follower_count); - res.user_info.following_count = format(res.user_info.following_count); - res.user_info.total_favorited = format(res.user_info.total_favorited); - $('#pagelet-user-info').html(MAIN_TMPL({ - data: res.user_info - })); - var stickyEl = $('.tab-wrap'); // 默认的tab选项 - - var listType = stickyEl.find('.tab').eq(0).attr('data-type'); - var stickyHolder = $('.video-tab'); - var rect = stickyEl.get(0).getBoundingClientRect(); - stickyHolder.attr('height', rect.height + 'px'); - var stickyT = stickyEl.get(0).offsetTop; - - window.onscroll = function (e) { - var scrollT = document.body.scrollTop; - - if (scrollT > stickyT) { - stickyEl.addClass('tab-box-fixed'); - } else { - stickyEl.removeClass('tab-box-fixed'); - } - }; - - getWorkList(listType); - bind(config); - }, - error: function error(e) { - console.log('error>', e); - }, - complete: function complete() { - isLoading = false; - } - }); -} - -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); - - _tea2.default.setEventCommonParams({ - page_name: 'reflow_user' - }); - - _tea2.default.send('page_view', {}); - - smsInvite(config.uid); - - var callback = function callback() { - goUser(config.uid); - }; - - renderMain(config); - banner.init({ - dl: DOWNLOAD_URL_SETTING['reflow-user'], - callback: isTypeSMS ? callback : null, - opts: { - type: 'user', - useDl: true, - params: { - uid: config.uid, - type: 'need_follow', - gd_label: 'profile_bottom' - } - }, - teaLogger: _tea2.default, - pageTag: 'reflow-user' - }); -} - -; - -/***/ }), - -/***/ "9740e2f820a158083b3c": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -__webpack_require__("c3dd550a9e17a9891063"); - -var tpl = __webpack_require__("f018444c1271e8d2d96d"); - -module.exports.show = function (msg, stayTime, el) { - var SHOW_TIME = stayTime || 2000; // 提示持续时间 2s - - var ANI_TIME = 500; // 提示消失时间 .5s - - el = el || $('body'); - var ihtml = tpl({ - tips: msg - }); - var $ihtml = $(ihtml); - $ihtml.appendTo($(el)); - /* 动画 */ - - setTimeout(function () { - $ihtml.addClass('show'); - }, 20); - setTimeout(function () { - $ihtml.addClass('removing'); - setTimeout(function () { - $ihtml.remove(); - }, ANI_TIME); - }, SHOW_TIME); -}; - -/***/ }), - -/***/ "a62155a7eb13f352b7e3": -/***/ (function(module, exports, __webpack_require__) { - -// extracted by mini-css-extract-plugin -module.exports = {"sms-code":"sms-code","pagelet-worklist":"pagelet-worklist","list":"list","music-item":"music-item","item-pic":"item-pic","pic-cover":"pic-cover","icon-music-play":"icon-music-play","item-content":"item-content","item-content-name":"item-content-name","item-content-used":"item-content-used","item-content-duration":"item-content-duration","item-opt":"item-opt","item":"item","cover":"cover","label":"label","digg":"digg","digg-icon":"digg-icon","digg-num":"digg-num","image-icon":"image-icon","empty":"empty","follow-num":"follow-num","tab-num":"tab-num","pagelet-user-info":"pagelet-user-info","bg":"bg","personal-card":"personal-card","info1":"info1","avatar":"avatar","focus-btn":"focus-btn","nickname":"nickname","shortid":"shortid","info2":"info2","verify-info":"verify-info","signature":"signature","extra-info":"extra-info","location":"location","constellation":"constellation","unknown":"unknown","follow-info":"follow-info","num":"num","text":"text","block":"block","video-tab":"video-tab","tab-wrap":"tab-wrap","tab":"tab","tab-box-fixed":"tab-box-fixed","active":"active","scaleImageWrapper":"scaleImageWrapper","close":"close","enlarge-wrapper":"enlarge-wrapper","full-screen":"full-screen","hidden":"hidden"}; - -/***/ }), - -/***/ "adf14ea98b2960ef189a": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -__webpack_require__("b0dbbc9fa6326f3e234c"); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - -function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - -var tmpRenderer = __webpack_require__("fd19f810c56546fb697a"); - -var BrowserOpen = /*#__PURE__*/function () { - function BrowserOpen() { - _classCallCheck(this, BrowserOpen); - - this.el = null; - } - - _createClass(BrowserOpen, [{ - key: "open", - value: function open() { - var _this = this; - - if (this.el) { - this.el.show(200); - return; - } - - var div = document.createElement('div'); - div.innerHTML = tmpRenderer(); - document.body.appendChild(div); - this.el = $('.open-browser'); - this.el.show(200); - var s; - this.el.click(function (ev) { - if (ev.target == _this.el[0]) { - _this.close(); - } - }); - } - }, { - key: "close", - value: function close() { - this.el.hide(200); - } - }]); - - return BrowserOpen; -}(); - -module.exports = BrowserOpen; - -/***/ }), - -/***/ "b0dbbc9fa6326f3e234c": -/***/ (function(module, exports, __webpack_require__) { - -// extracted by mini-css-extract-plugin - -/***/ }), - -/***/ "c3dd550a9e17a9891063": -/***/ (function(module, exports, __webpack_require__) { - -// extracted by mini-css-extract-plugin -module.exports = {"pagelet-tips":"pagelet-tips","show":"show","removing":"removing"}; - -/***/ }), - -/***/ "d712b069d922fb6d94e3": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -__webpack_require__("12a5c4dc26e1824fdd8b"); - -/** -* config.dl 短链 -* config.opts 跳转参数 -* teaLogger tea对象 -* pageTag 页面标识 -*/ -// var ga = require('common/ga'); -var downloadAction = __webpack_require__("166003ecd7694b2377b6"); - -function bind(config) { - $('#download').on('click', function (e) { - e.preventDefault(); // if(config.pageTag) - // ga.gaevent(config.pageTag,'download_bottom',''); - - if (config.teaLogger) { - config.teaLogger.send('tap', { - type: 'download_bottom' - }); - } - - if (typeof config.callback === 'function') { - config.callback(); - } else { - downloadAction.downloadApp(config.dl, config.opts); - } - }); -} - -module.exports = { - init: function init(config) { - bind(config); - } -}; - -/***/ }), - -/***/ "e844fe34c02b3cce5ed3": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = { - dateFormat: function dateFormat(timestamp, format) { - if (!timestamp) { - return; - } - - if (typeof timestamp !== 'number') { - try { - timestamp = Number(timestamp); - } catch (error) { - throw Error('Expected to be a number.', error); - } - } - - function zeroize(num) { - if (num && typeof num !== 'number') { - return; - } - - return num < 10 ? "0".concat(num) : num; - } - - var date = new Date(timestamp); - var format = format || 'YYYY年MM月DD日'; - var now = Number(new Date()); - var diff = now - timestamp; - var year = date.getFullYear(); - var month = date.getMonth() + 1; - var day = date.getDate(); - var hours = date.getHours(); - var minute = date.getMinutes(); - var second = date.getSeconds(); - var _seconds = 1000; - - var _minute = 60 * _seconds; - - var _hours = 60 * _minute; - - var _day = 24 * _hours; - - var formatArray = ['YYYY', 'MM', 'DD', 'H', 'M', 'S']; - var dateArray = [year, month, day, hours, minute]; - - if (diff > _day * 3) { - if (diff < _day * 366) { - format = 'MM月DD日'; - } - - for (var i = 0; i < formatArray.length - 1; i++) { - format = format.replace(formatArray[i], dateArray[i]); - } - - return format; - } else { - if (parseInt(diff / _day, 10) >= 1 && parseInt(diff / _day, 10) < 3) { - return "".concat(parseInt(diff / _day, 10), "\u5929\u524D"); - } else if (parseInt(diff / _hours, 10) >= 1) { - return "".concat(parseInt(diff / _hours, 10), "\u5C0F\u65F6\u524D"); - } else if (parseInt(diff / _minute, 10) >= 1) { - return "".concat(parseInt(diff / _minute, 10), "\u5206\u949F\u524D"); - } else { - return '刚刚'; - } - } - }, - getUrlParameter: function getUrlParameter(sParam) { - var sPageURL = decodeURIComponent(window.location.search.substring(1)); - var sURLVariables = sPageURL.split('&'); - var sParameterName, i; - - for (i = 0; i < sURLVariables.length; i++) { - sParameterName = sURLVariables[i].split('='); - - if (sParameterName[0] === sParam) { - return sParameterName[1] === undefined ? true : sParameterName[1]; - } - } - }, - htmlEntities: function htmlEntities(str) { - return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); - }, - - /* - * 获取url参数 - */ - getUrlParam: function getUrlParam(location, name) { - var url = location; - var splitIndex = url.indexOf('?') + 1; - var paramStr = url.substr(splitIndex, url.length); - var arr = paramStr.split('&'); - - for (var i = 0; i < arr.length; i++) { - var kv = arr[i].split('='); - - if (kv[0] == name) { - return kv[1]; - } - } - - return ''; - }, - - /* - * 设置url参数 - */ - setUrlParam: function setUrlParam(location, name, value) { - var url = location; - var splitIndex = url.indexOf('?') + 1; - var paramStr = url.substr(splitIndex, url.length); - var newUrl = url.substr(0, splitIndex); // - if exist , replace - - var arr = paramStr.split('&'); - - for (var i = 0; i < arr.length; i++) { - var kv = arr[i].split('='); - - if (kv[0] == name) { - newUrl += "".concat(kv[0], "=").concat(value); - } else { - if (kv[1] != undefined) { - newUrl += "".concat(kv[0], "=").concat(kv[1]); - } - } - - if (i != arr.length - 1) { - newUrl += '&'; - } - } // - if new, add - - - if (newUrl.indexOf(name) < 0) { - newUrl += splitIndex == 0 ? "?".concat(name, "=").concat(value) : "&".concat(name, "=").concat(value); - } - - return newUrl; - }, - stopEvent: function stopEvent(event, prevent) { - event.stopPropagation(); - event.stopImmediatePropagation(); - - if (prevent !== false) { - event.preventDefault(); - } - }, - setScroll: function setScroll(scrollable, el) { - el = el || document; - - if (scrollable) { - $(el).off('touchmove'); - } else { - $(el).off('touchmove').on('touchmove', function (e) { - this.stopEvent(e); - }); - } - }, - initScrollEvents: function initScrollEvents(offset, debounce) { - var scrollEndTimer; - offset = offset || 100; - - function globalScroll(e) { - var theDocumentHeight = document.body.scrollHeight; - var scrollY = document.documentElement.scrollTop || document.body.scrollTop; - - if (scrollY >= theDocumentHeight - window.innerHeight - offset) { - $(window).trigger('scrollBottom', e.type); - - if (e.type == 'scroll') { - if (scrollEndTimer) { - clearTimeout(scrollEndTimer); - } - - scrollEndTimer = setTimeout(function () { - $(window).trigger('scrollEnd'); - }, debounce || 50); - } - } - } - - $(window).on('scroll load afterflow', globalScroll); - }, - - /** - * 获取对象深层的值 - * @params {Object} obj - * @params {String} path - * eg. getDeepValue(obj, 'a.b.c[0].name') - */ - getDeepValue: function getDeepValue(obj, path) { - var current = obj || {}; - var temp = path.split('.'); - var paths = []; - var match = []; - temp.forEach(function (key) { - if (match = key.match(/(\w+)\[(\d+)\]/)) { - paths.push(match[1]); - paths.push(match[2]); - } else { - paths.push(key); - } - }); - - for (var i = 0, len = paths.length; i < len; i++) { - var key = paths[i]; - - if (current[key] === null || current[key] === undefined) { - return undefined; - } - - current = current[key]; - } - - return current; - }, - getTTWebId: function () { - var ttwebid = $.cookie('tt_webid') || String(parseInt(Math.random() * 10000)); - return function () { - return ttwebid; - }; - }(), - throttle: function throttle(fn) { - var interval = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1000; - var throttleLastTime = null; - return function () { - var ctx = this; - var args = arguments; - var nowTime = Number(new Date()); - - if (nowTime - throttleLastTime > interval || !throttleLastTime) { - fn.apply(ctx, args); - throttleLastTime = nowTime; - } - }; - }, - debounce: function debounce(fn) { - var interval = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1000; - var debounceTimeout = null; - return function () { - var ctx = this; - var args = arguments; - - if (debounceTimeout) { - clearTimeout(debounceTimeout); - debounceTimeout = null; - } - - debounceTimeout = setTimeout(function () { - fn.apply(ctx, args); - }, interval); - }; - }, - getBrowserType: function getBrowserType() { - var _navigator = navigator, - userAgent = _navigator.userAgent; // 取得浏览器的userAgent字符串 - - var isOpera = userAgent.indexOf('Opera') > -1; // 判断是否Opera浏览器 - - var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 && !isOpera; // 判断是否IE浏览器 - - var isEdge = userAgent.indexOf('Edge') > -1; // 判断是否IE的Edge浏览器 - - var isFF = userAgent.indexOf('Firefox') > -1; // 判断是否Firefox浏览器 - - var isSafari = userAgent.indexOf('Safari') > -1 && userAgent.indexOf('Chrome') == -1; // 判断是否Safari浏览器 - - var isChrome = userAgent.indexOf('Chrome') > -1 && userAgent.indexOf('Safari') > -1; // 判断Chrome浏览器 - - if (isIE) { - return 'IE'; - } - - if (isOpera) { - return 'Opera'; - } - - if (isEdge) { - return 'Edge'; - } - - if (isFF) { - return 'FF'; - } - - if (isSafari) { - return 'Safari'; - } - - if (isChrome) { - return 'Chrome'; - } - - return 'other'; - } -}; - -/***/ }), - -/***/ "f018444c1271e8d2d96d": -/***/ (function(module, exports) { - -module.exports = function(obj) { -obj || (obj = {}); -var __t, __p = ''; -with (obj) { -__p += '
    ' + -((__t = ( tips )) == null ? '' : __t) + -'
    \n'; - -} -return __p -} - -/***/ }), - -/***/ "f1e83373459b7c5ed036": -/***/ (function(module, exports) { - -module.exports = function(obj) { -obj || (obj = {}); -var __t, __p = '', __j = Array.prototype.join; -function print() { __p += __j.call(arguments, '') } -with (obj) { -__p += '
    \n \n 关注\n

    ' + -((__t = ( data.nickname )) == null ? '' : __t) + -'

    \n

    抖音ID:\n '; - if (data.unique_id) {; -__p += '\n ' + -((__t = ( data.unique_id )) == null ? '' : __t) + -'\n '; - }else{ ; -__p += '\n ' + -((__t = ( data.short_id )) == null ? '' : __t) + -'\n '; - } ; -__p += '\n

    \n
    \n
    \n '; - if (data.custom_verify){ ; -__p += '\n
    \n \n '; - if (data.verification_type == 2) { ; -__p += '\n 抖音音乐人\n '; - } else if (data.custom_verify) { ; -__p += '\n ' + -((__t = ( data.custom_verify )) == null ? '' : __t) + -'\n '; - } ; -__p += '\n \n
    \n '; - } ; -__p += '\n

    ' + -((__t = ( data.signature || '先定一个能达到的小目标,比方说来句签名' )) == null ? '' : __t) + -'

    \n

    \n \n ' + -((__t = ( data.following_count )) == null ? '' : __t) + -'\n 关注\n \n \n ' + -((__t = ( data.follower_count )) == null ? '' : __t) + -'\n 粉丝\n \n \n ' + -((__t = ( data.total_favorited )) == null ? '' : __t) + -'\n \n \n

    \n
    \n\n
    \n '; - if (data.verification_type == 2){ ; -__p += '\n
    \n
    音乐' + -((__t = ( data.original_musician.music_count )) == null ? '' : __t) + -'
    \n
    作品' + -((__t = ( data.aweme_count )) == null ? '' : __t) + -'
    \n '; - }else { ; -__p += '\n
    \n
    作品' + -((__t = ( data.aweme_count )) == null ? '' : __t) + -'
    \n '; - } ; -__p += '\n \n
    \n
    '; - -} -return __p -} - -/***/ }), - -/***/ "fd19f810c56546fb697a": -/***/ (function(module, exports) { - -module.exports = function(obj) { -obj || (obj = {}); -var __t, __p = ''; -with (obj) { -__p += '
    \n
    \n
    链接打不开?
    \n

    请点击右上角

    \n

    选择在“浏览器”中打开

    \n
    \n
    \n
    '; - -} -return __p -} - -/***/ }) - -/******/ }); -//# sourceMappingURL=index.f2516941.js.map \ No newline at end of file diff --git "a/API/API\345\217\202\350\200\203.md" "b/API/API\345\217\202\350\200\203.md" deleted file mode 100644 index 47300959..00000000 --- "a/API/API\345\217\202\350\200\203.md" +++ /dev/null @@ -1,34 +0,0 @@ -# API参考 - -var schemas = { - home: 'snssdk1128://feed?refer=web&gd_label={{gd_label}}', - // 首页feed - detail: 'snssdk1128://aweme/detail/{{id}}?refer=web&gd_label={{gd_label}}&appParam={{appParam}}&needlaunchlog=1', - // 作品详情页 - user: 'snssdk1128://user/profile/{{uid}}?refer=web&gd_label={{gd_label}}&type={{type}}&needlaunchlog=1', - // 用户主页 - challenge: 'snssdk1128://challenge/detail/{{id}}?refer=web&is_commerce=0', - // 挑战详情 - music: 'snssdk1128://music/detail/{{id}}?refer=web', - // 音乐详情 - live: 'snssdk1128://live?room_id={{room_id}}&user_id={{user_id}}&u_code={{u_code}}&from=webview&refer=web', - // 直播间 - webview: 'snssdk1128://webview?url={{url}}&from=webview&refer=web', - // webview - webview_fullscreen: 'snssdk1128://webview?url={{url}}&from=webview&hide_nav_bar=1&refer=web', - // webview 沉浸式 - poidetail: 'snssdk1128://poi/detail?id={{id}}&from=webview&refer=web', - // poi详情页 - forward: 'snssdk1128://forward/detail/{{id}}', - // 转发详情页 - billboard_word: 'snssdk1128://search/trending', - // 热搜词榜 - billboard_video: 'snssdk1128://search/trending?type=1', - // 热搜视频榜 - billboard_music: 'snssdk1128://search/trending?type=2', - // 热搜音乐榜 - billboard_positive: 'snssdk1128://search/trending?type=3', - // 正能量榜 - billboard_star: 'snssdk1128://search/trending?type=4' // 明星榜 - -}; // universal link url \ No newline at end of file diff --git a/API/Server.txt b/API/Server.txt deleted file mode 100644 index 8356b57c..00000000 --- a/API/Server.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(1, 4, 2, 2), - prodvers=(1, 4, 2, 2), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'本地解析服务'), - StringStruct(u'FileVersion', u'1.4.2.2'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'本地解析服务'), - StringStruct(u'ProductVersion', u'1.4.2.2')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/API/TikTokAPI.py b/API/TikTokAPI.py deleted file mode 100644 index f44b4a6b..00000000 --- a/API/TikTokAPI.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:TikTokAPI -@Date :2021/01/29 13:34:54 -@Author :JohnserfSeed -@version :1.0 -@License :(C)Copyright 2017-2020, Liugroup-NLPR-CASIA -@Mail :johnserfseed@gmail.com -''' - -import requests,json,re,os -from TikTokMulti import TikTok - - -class TikTokAPI(): - - def __init__(self) -> None: - self.PCheader = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'} - self.iPXheader = {'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'} - #抖音热榜API - self.Billboard_Share = 'https://www.iesdouyin.com/share/billboard/?id=0' - self.Billboard_Hot = 'https://aweme-hl.snssdk.com/aweme/v1/hot/search/list/?detail_list=0&mac_address=08:00:27:29:D2:F5&os_api=23&device_type=MI%205s&device_platform=android&ssmix=a&iid=92152480453&manifest_version_code=860&dpi=320&uuid=008796750074613&version_code=860&app_name=aweme&version_name=8.6.0&ts=1577932778&openudid=c055533a0591b2dc&device_id=69918538596&resolution=810*1440&os_version=6.0.1&language=zh&device_brand=Xiaomi&app_type=normal&ac=wifi&update_version_code=8602&aid=1128&channel=tengxun_new&_rticket=1577932779592' - self.Billboard_Video = 'https://aweme-hl.snssdk.com/aweme/v1/hot/search/list/?detail_list=1&mac_address=08:00:27:29:D2:F5&os_api=23&device_type=MI%205s&device_platform=android&ssmix=a&iid=92152480453&manifest_version_code=860&dpi=320&uuid=008796750074613&version_code=860&app_name=aweme&version_name=8.6.0&ts=1577932778&openudid=c055533a0591b2dc&device_id=69918538596&resolution=810*1440&os_version=6.0.1&language=zh&device_brand=Xiaomi&app_type=normal&ac=wifi&update_version_code=8602&aid=1128&channel=tengxun_new&_rticket=1577932779592' - pass - - def Get_Tk_Billboard(self): - - pass - - def Set_Tk_Billboard(self): - - pass - - -if __name__ == "__main__": - pass \ No newline at end of file diff --git a/API/TikTokDownloadVersion.txt b/API/TikTokDownloadVersion.txt deleted file mode 100644 index fbf70351..00000000 --- a/API/TikTokDownloadVersion.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(1, 4, 2, 2), - prodvers=(1, 4, 2, 2), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'抖音单视频下载'), - StringStruct(u'FileVersion', u'1.4.2.2'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'抖音单视频下载'), - StringStruct(u'ProductVersion', u'1.4.2.2')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/API/TikTokGUIVersion.txt b/API/TikTokGUIVersion.txt deleted file mode 100644 index 79469212..00000000 --- a/API/TikTokGUIVersion.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(2, 1, 1, 0), - prodvers=(2, 1, 1, 0), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'抖音批量下载-图形界面'), - StringStruct(u'FileVersion', u'2.1.1.0'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'抖音批量下载-图形界面'), - StringStruct(u'ProductVersion', u'2.1.1.0')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/API/TikTokLive.txt b/API/TikTokLive.txt deleted file mode 100644 index 81166a95..00000000 --- a/API/TikTokLive.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(1, 4, 2, 2), - prodvers=(1, 4, 2, 2), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'抖音直播推流抓取'), - StringStruct(u'FileVersion', u'1.4.2.2'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'抖音直播推流抓取'), - StringStruct(u'ProductVersion', u'1.4.2.2')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/API/TikTokMultiVersion.txt b/API/TikTokMultiVersion.txt deleted file mode 100644 index 4e2da532..00000000 --- a/API/TikTokMultiVersion.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(1, 2, 5, 30), - prodvers=(1, 2, 5, 30), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'抖音批量下载'), - StringStruct(u'FileVersion', u'1.2.5.30'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2022 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'抖音批量下载'), - StringStruct(u'ProductVersion', u'1.2.5.30')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/API/TikTokPicVersion.txt b/API/TikTokPicVersion.txt deleted file mode 100644 index d9cac668..00000000 --- a/API/TikTokPicVersion.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(1, 4, 2, 2), - prodvers=(1, 4, 2, 2), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'抖音图集下载'), - StringStruct(u'FileVersion', u'1.0.0.0'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'抖音图集下载'), - StringStruct(u'ProductVersion', u'1.0.0.0')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/API/TikTokTool.txt b/API/TikTokTool.txt index f325bdb3..ade4452d 100644 --- a/API/TikTokTool.txt +++ b/API/TikTokTool.txt @@ -31,10 +31,10 @@ VSVersionInfo( u'080404b0', [StringStruct(u'CompanyName', u'JohnserfSeed'), StringStruct(u'FileDescription', u'抖音视频批量下载'), - StringStruct(u'FileVersion', u'1.4.2.2'), + StringStruct(u'FileVersion', u'1.5.0.0'), StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), StringStruct(u'ProductName', u'抖音视频批量下载'), - StringStruct(u'ProductVersion', u'1.4.2.2')]) + StringStruct(u'ProductVersion', u'1.5.0.0')]) ]), VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) ] diff --git a/API/TikTokUpdata.txt b/API/TikTokUpdata.txt deleted file mode 100644 index 69eaac42..00000000 --- a/API/TikTokUpdata.txt +++ /dev/null @@ -1,41 +0,0 @@ -# UTF-8 -# -# For more details about fixed file info 'ffi' see: -# http://msdn.microsoft.com/en-us/library/ms646997.aspx -VSVersionInfo( - ffi=FixedFileInfo( - # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) - # Set not needed items to zero 0. - filevers=(1, 0, 0, 0), - prodvers=(1, 0, 0, 0), - # Contains a bitmask that specifies the valid bits 'flags'r - mask=0x3f, - # Contains a bitmask that specifies the Boolean attributes of the file. - flags=0x0, - # The operating system for which this file was designed. - # 0x4 - NT and there is no need to change it. - OS=0x40004, - # The general type of file. - # 0x1 - the file is an application. - fileType=0x1, - # The function of the file. - # 0x0 - the function is not defined for this fileType - subtype=0x0, - # Creation date and time stamp. - date=(0, 0) - ), - kids=[ - StringFileInfo( - [ - StringTable( - u'080404b0', - [StringStruct(u'CompanyName', u'JohnserfSeed'), - StringStruct(u'FileDescription', u'TikTokTool更新程序'), - StringStruct(u'FileVersion', u'1.0.0.0'), - StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), - StringStruct(u'ProductName', u'TikTokTool更新程序'), - StringStruct(u'ProductVersion', u'1.0.0.0')]) - ]), - VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) - ] -) \ No newline at end of file diff --git a/Collection/CopyWritingHomePage_1.json b/Collection/CopyWritingHomePage_1.json deleted file mode 100644 index 5a9bcce5..00000000 --- a/Collection/CopyWritingHomePage_1.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "User_0": - { - "uid":"", - "description":"", - "url": - [ - "" - ] - } -} \ No newline at end of file diff --git a/Collection/GirlHomePage_1.json b/Collection/GirlHomePage_1.json deleted file mode 100644 index 94f716b0..00000000 --- a/Collection/GirlHomePage_1.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "0": - { - "uid":"小e同学", - "description":"好看的小姐姐", - "url": - [ - "https://v.douyin.com/JcjJ5Tq/" - ] - }, - "1": - { - "uid":"小橙子", - "description":"好看的小姐姐", - "url": - [ - "https://v.douyin.com/evLNohM/" - ] - }, - "2": - { - "uid":"黑色闪光", - "description":"好看的小姐姐", - "url": - [ - "https://v.douyin.com/evLhSNB/" - ] - } -} \ No newline at end of file diff --git a/Collection/MusicHomePage_1.json b/Collection/MusicHomePage_1.json deleted file mode 100644 index bb1d950d..00000000 --- a/Collection/MusicHomePage_1.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "User_0": - { - "uid":"楊老x", - "description":"抖音翻唱", - "url": - [ - "https://v.douyin.com/Jcn71nB/" - ] - }, - "User_1": - { - "uid":"花花花菜", - "description":"抖音翻唱", - "url": - [ - "https://v.douyin.com/Jcn7yaQ/" - ] - } -} \ No newline at end of file diff --git a/DB/create.sql b/DB/create.sql deleted file mode 100644 index df31c1af..00000000 --- a/DB/create.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- SQLiteStudio v3.4.3 生成的文件,周一 2月 13 23:47:22 2023 --- --- 所用的文本编码:UTF-8 --- -PRAGMA foreign_keys = off; -BEGIN TRANSACTION; - --- 表:aweme -CREATE TABLE IF NOT EXISTS aweme ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - aweme_collect_count TEXT NOT NULL, - aweme_comment_count TEXT NOT NULL, - aweme_creat_time TEXT (10) NOT NULL, - aweme_desc TEXT NOT NULL, - aweme_digg_count TEXT NOT NULL, - aweme_nickname TEXT NOT NULL, - aweme_music_uri TEXT (19) NOT NULL, - aweme_play_count TEXT NOT NULL, - aweme_sec_uid TEXT (19) NOT NULL, - aweme_share_count TEXT NOT NULL, - aweme_type INTEGER (3) NOT NULL, - aweme_unique_id TEXT NOT NULL, - aweme_user_age INTEGER (3) NOT NULL -); - - -COMMIT TRANSACTION; -PRAGMA foreign_keys = on; diff --git a/Logo.ico b/Logo.ico deleted file mode 100644 index b8bdff18..00000000 Binary files a/Logo.ico and /dev/null differ diff --git a/README-EN.md b/README-EN.md index 9f536bb0..88b992c5 100644 --- a/README-EN.md +++ b/README-EN.md @@ -114,7 +114,7 @@ ## 🖥 Supported Operating Systems
    - List of Operating Systems supported by version 1.4.2.2 + List of Operating Systems supported by version 1.5.0.0 - Windows 11 - Windows 10 Version 1809 (OS Build 17763) or later diff --git a/README.md b/README.md index 28f02884..6d9ac9bd 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ [![GitHub Issues](https://img.shields.io/github/issues/johnserf-seed/tiktokdownload?style=social)](https://github.com/Johnserf-Seed/TikTokDownload) [![GitHub Closed Issues](https://img.shields.io/github/issues-closed/johnserf-seed/tiktokdownload?style=social)](https://github.com/Johnserf-Seed/TikTokDownload) +[![F2 Downloads](https://pepy.tech/badge/f2/month)](https://pepy.tech/project/f2) +[![PyPI version](https://badge.fury.io/py/f2.svg)](https://badge.fury.io/py/f2) [![jsDelivr monthly hits](https://data.jsdelivr.com/v1/package/gh/Johnserf-Seed/TikTokDownload/badge)](https://www.jsdelivr.com/package/gh/Johnserf-Seed/TikTokDownload) [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FJohnserf-Seed%2FTikTokDownload&count_bg=%235FFFFF&title_bg=%23FB1953&icon=tiktok.svg&icon_color=%23250C1F&title=view&edge_flat=false)](https://hits.seeyoufarm.com) -[![Discord](https://img.shields.io/discord/1070512513889878067?color=5865F2&logo=discord&logoColor=white?style=for-the-badge)](https://discord.gg/q3hA8qQZbG) +[![Discord](https://img.shields.io/discord/1146473603450282004?color=5865F2&logo=discord&logoColor=white?style=for-the-badge)](https://discord.gg/3PhtPmgHf8) [![Patreon](https://img.shields.io/badge/Patreon-TikTokDownload-red.svg?style=flat&logo=patreon)](https://www.patreon.com/TikTokDownload713)
    @@ -29,15 +31,15 @@ ## 🚀 环境准备/Environment > [![Microsoft 应用商店](https://tvax1.sinaimg.cn/large/006908GAly1hgn87jhad8j305001qa9y.jpg)](https://aka.ms/terminal) -> +> > 旧的控制台无法很好适配,推荐使用Windows Terminal。 -> +> > [![Python v3.11.1](https://www.python.org/static/img/python-logo.png)](https://www.python.org/ftp/python/3.11.1/python-3.11.1-amd64.exe) -> +> > Python3.11.1 低于该版本可能会有意外的错误 -> +> > [![GitHub 发行版](https://tvax2.sinaimg.cn/large/006908GAly1hh16psn51aj30a5020gly.jpg)](https://github.com/Johnserf-Seed/TikTokDownload/releases) -> +> > 发行版下载 每次Commits之后会重新打包
    ⚙ Windows Terminal 设置(必看) @@ -49,8 +51,26 @@ ## 🧰 功能/Features -- DouYin 接口信息 +- DouYin 接口 + - ✅ 下载发布作品。 + - ✅ 下载收藏作品。 + - ✅ 下载喜欢作品。 + - ✅ 下载图集作品。 + - ✅ 下载作品封面。 + - ✅ 下载作品文案。 + - ✅ 下载作品原声。 + - ✅ 下载直播。 + - ✅ 下载合集作品。 + - ⌛ 下载关注作品。 + - ⌛ 下载好友作品。 + - ⌛ 下载推荐作品。 + - ⌛ 下载相关推荐作品。 + - ⌛ 提取评论。 + - ✅ 详细直播间信息。 + - ⌛ 直播间弹幕发言。 - ✅ 详细用户信息。 + +- TikTok 接口 - ✅ 下载发布作品。 - ✅ 下载收藏作品。 - ✅ 下载喜欢作品。 @@ -58,69 +78,92 @@ - ✅ 下载作品封面。 - ✅ 下载作品文案。 - ✅ 下载作品原声。 - - ✅ 提取直播链接。 + - ✅ 下载合集作品。 - ⌛ 下载关注作品。 - ⌛ 下载好友作品。 - ⌛ 下载推荐作品。 - - ❌ 下载合集作品。 - - ❌ 提取评论。 + - ⌛ 下载相关推荐作品。 + - ⌛ 提取评论。 + - ⌛ 详细直播间信息。 + - ⌛ 直播间弹幕发言。 + - ✅ 详细用户信息。 + +- 更多接口,请查阅 [「F2开发者接口」](https://johnserf-seed.github.io/f2/guide/apps/douyin/) - 异步下载 - - ✅ 同时下载和处理多个作品,提高效率。 - - ✅ 调节异步线程,减轻系统压力减少接口出错。 - - ✅ 调节网络并发数,减少被服务器校验 + - ✅ 同时处理和下载多个作品,提高效率。 + - ✅ 异步线程,减轻系统压力减少接口出错。 + - ✅ 网络并发数,减少被服务器校验 + - ✅ 超时重试设置,降低采集错误率。 + +- Cookie + - ✅ SSO扫码登录。 + - ✅ 使用--auto-cookie自动从浏览器获取cookie。 -- Cookie 管理 - - ✅ 生成web所需 cookie 值,便于访问需要登录的接口。 - - ✅ 处理 SetCookie。 +- 接口数据模型 + - ✅ 开箱即用的接口参数配置,提供完整参数生成。 + - 请查阅[「使用接口模型生成XB参数」](https://johnserf-seed.github.io/f2/guide/apps/douyin/#%E4%BD%BF%E7%94%A8%E6%8E%A5%E5%8F%A3%E6%A8%A1%E5%9E%8B%E7%94%9F%E6%88%90xb%E5%8F%82%E6%95%B0-%F0%9F%9F%A2) -- 配置文件操作 +- 接口数据过滤器 + - ✅ 通过过滤接口数据,提高采集与数据处理效率。 + - 请查阅[「过滤器采集数据」](https://johnserf-seed.github.io/f2/guide/apps/douyin/#%E4%BD%BF%E7%94%A8%E6%8E%A5%E5%8F%A3%E6%A8%A1%E5%9E%8B%E7%94%9F%E6%88%90xb%E5%8F%82%E6%95%B0-%F0%9F%9F%A2) + +- 更多功能 - ✅ 长短链解析。 - ✅ 自定义保存目录。 - ✅ 是否下载原声。 - - ✅ 是否自动更新。 + - ✅ 是否下载封面。 + - ✅ 是否下载文案。 + - ✅ 单次下载作品数量。 + - ✅ 单次返回作品数量。 + - ✅ 自定义文件名模板。 + - ✅ 代理设置。 - ✅ 指定下载时间区间。 - - ❌ 设定下载作品点赞阈值。 - - ❌ 设定下载作品播放阈值。 + - ⌛ 设定下载作品点赞阈值。 + - ⌛ 设定下载作品播放阈值。 + + - 更多配置文件操作,请查阅[「F2配置文件」](https://johnserf-seed.github.io/f2/site-config.html) + +- 多用户配置 + - ✅ 对不同用户建立不同配置与不同的下载模式。 + - ✅ 灵活的cli模式,方便用户快速切换配置。 + - ⌛ 灵活的webui模式,方便用户快速切换配置。 - 版本更新 - - ✅ 提供自动检查和下载新版本的功能。 + - ✅ 全新开发 F2 依赖pip进行升级 - 文件检查 - ✅ 下载文件前检查文件是否已经存在,避免重复下载。 + - ⌛ 添加”黑名单“,不检查这些作品是否存在。 - 命令行交互 - - ✅ 提供命令行选项和全局 headers,便于用户操作。 + - ✅ 提供cli模式与开发者模式 - ⌛ 提供webui模式 -- 扫码登录 - - ✅ 提供扫码登录的功能,无需手动填写 cookie。 - - 自动重命名 - - ✅ 使用昵称映射表确保不重复下载改名作者的作品。 - - ⌛ 使用作品文案映射表确保不重复下载改文案的作品。 + - ✅ 无需关心用户是否改名,从而造成重复下载。 + - ⌛ 无需关心作品是否改文案,从而造成重复下载。 - 本地加密参数调用 - - ✅ XBogus - - ✅ verifyFp - - ✅ s_v_web_id - - ✅ ttwid - - ✅ x-tt-params - - 🔘 msToken + - ✅ XBogus(dy、tk) + - ✅ verifyFp(dy) + - ✅ s_v_web_id(dy) + - ✅ ttwid(dy、tk) + - ✅ x-tt-params(tk) + - ✅ msToken(dy、tk) + - ✅ odin_tt(tk) ## 💡 待办/ToDo -- 适配TikTok - 创建自动化任务 -- 多用户解析 - [更多请查看项目板](https://github.com/users/Johnserf-Seed/projects/1/views/1) ## 🖥 支持的操作系统/Supported Operating Systems
    - 1.4.2.2 支持的操作系统列表 + TikTokDownload 1.5 / F2 0.0.1-pw1 支持的操作系统列表 - Windows 11 - Windows 10 版本 1809(OS 内部版本 17763)或更高版本 @@ -136,6 +179,22 @@
    +## 📥 安装与运行/Installation and Running + +1. **📦 安装/Installation** + +请查阅 [「安装」](https://johnserf-seed.github.io/f2/install.html) + +2. **▶️ 运行/Running** +请查阅 [「配置文件」](https://johnserf-seed.github.io/f2/install.html) + +3. **🔬 测试/Test** + +在 F2 安装目录打开终端运行,如果配置正确那么你不会看见报错。 +```bash +python -m pytest +``` + ## 📸 运行过程/Running Process
    @@ -153,41 +212,6 @@ https://user-images.githubusercontent.com/40727745/12c21d55-b629-485a-b904-54d86
    -## 📥 安装与运行/Installation and Running - -1. **📦 安装/Installation** - - -```bash -python -m venv venv -.\venv\Scripts\activate -pip install -r requirements.txt -``` - -2. **▶️ 运行/Running** - - -```python -import Util - -if __name__ == '__main__': - # 获取命令行和配置文件 - cmd = Util.Command() - config = cmd.config_dict - dyheaders = cmd.dyheaders - - # 异步下载作品 - Util.asyncio.run(Util.Profile(config, dyheaders).get_Profile()) - input("[ 提示 ]:下载完成,输入任意键退出。") -``` - -3. **🔬 测试/Test** - -```bash -python example.py -``` - - ## 🗂️ 项目结构/Folder
    @@ -200,17 +224,7 @@ python example.py │ └── -------.md │ ├─ API -│ ├── API.js -│ ├── API参考.md -│ ├── Server.txt -│ ├── TikTokAPI.py -│ ├── TikTokDownloadVersion.txt -│ ├── TikTokGUIVersion.txt -│ ├── TikTokLive.txt -│ ├── TikTokMultiVersion.txt -│ ├── TikTokPicVersion.txt │ ├── TikTokTool.txt -│ ├── TikTokUpdata.txt │ ├── user_base_info.json │ ├── user_post_delete.json │ ├── user_post_detail.json @@ -218,15 +232,7 @@ python example.py │ ├── user_post_info_video.json │ └── user_profile_info.json │ -├─ Collection -│ ├── CopyWritingHomePage_1.json -│ ├── GirlHomePage_1.json -│ └── MusicHomePage_1.json -│ -├─ DB -│ └── create.sql -│ -├─ GUI +├─ GUI(待重构) │ ├── Main.ui │ ├── preview.png │ ├── README-EN.md @@ -235,25 +241,13 @@ python example.py │ ├── resource.py │ └── Resource.qrc │ -└─ Util - ├── Check.py - ├── Command.py - ├── Config.py - ├── Cookies.py - ├── Download.py - ├── Lives.py - ├── Log.py - ├── Login.py - ├── NickMapper.py - ├── Profile.py - ├── Resource.py - ├── Urls.py - ├── XB.py - ├── __init__.py - ├── __version__.py +└─ Server └─ algorithm + ├── build-win.bat ├── package.json + ├── requirements.txt ├── Server.py + ├── Server.txt ├── s_v_web_id.js ├── s_v_web_id.py ├── x-bogus.js @@ -262,22 +256,15 @@ python example.py ├─ .gitignore ├─ Banner.png ├─ build-win.bat -├─ conf.conf -├─ conf.ini ├─ Dockerfile -├─ example.py -├─ info.db ├─ LICENSE -├─ Logo.ico +├─ f2-logo.ico ├─ README-EN.md ├─ README.md ├─ requirements.txt -├─ server.bat -├─ server.sh -├─ TikTokLive.py -├─ TikTokMultiGUI.py +├─ run-server.bat +├─ run-server.sh ├─ TikTokTool.py -├─ TikTokUpdata.py ├─ version └─ _config.yml @@ -290,7 +277,7 @@ python example.py ![赞赏](https://user-images.githubusercontent.com/40727745/217866800-23980dc1-f3ce-4bc7-b192-518651fef8da.png) -感谢对本项目的支持!如果您觉得这个项目有帮助,欢迎赞助。您可以直接访问我们的 [![Patreon](https://img.shields.io/badge/Patreon-TikTokDownload-red.svg?style=flat&logo=patreon)](https://www.patreon.com/TikTokDownload713) +感谢对本项目的支持!如果您觉得这个项目有帮助,欢迎赞助。您可以直接访问我们的 [![Patreon](https://img.shields.io/badge/Patreon-F2-red.svg?style=flat&logo=patreon)](https://www.patreon.com/F2_pypi) ## 📧 联系/Contact @@ -303,15 +290,19 @@ python example.py ## 🙏 鸣谢/Acknowledgments - [Windows Terminal](https://aka.ms/terminal) -- [aiohttp](https://github.com/aio-libs/aiohttp) -- [requests](https://github.com/psf/requests) -- [Pillow (PIL Fork)](https://github.com/python-pillow/Pillow) -- [lxml](https://github.com/lxml/lxml) +- [Python](https://www.python.org/) +- [httpx](https://github.com/encode/httpx) +- [click](https://github.com/pallets/click) +- [aiofiles](https://github.com/Tinche/aiofiles) +- [aiosqlite](https://github.com/omnilib/aiosqlite) - [rich](https://github.com/willmcgugan/rich) - [qrcode](https://github.com/lincolnloop/python-qrcode) -- [ConfigObj](https://github.com/DiffSK/configobj) +- [pyyaml](hhttps://github.com/yaml/pyyaml) +- [jsonpath-ng](https://github.com/h2non/jsonpath-ng) +- [m3u8](https://github.com/globocom/m3u8) +- [pytest](https://github.com/pytest-dev/pytest) -对于他们的贡献和努力,我们表示由衷的感谢。 +对于他们的贡献和努力,表示由衷的感谢。 ## ⚖️ 免责声明/Disclaimer diff --git a/Server/Server.txt b/Server/Server.txt index 8356b57c..fea3c602 100644 --- a/Server/Server.txt +++ b/Server/Server.txt @@ -31,10 +31,10 @@ VSVersionInfo( u'080404b0', [StringStruct(u'CompanyName', u'JohnserfSeed'), StringStruct(u'FileDescription', u'本地解析服务'), - StringStruct(u'FileVersion', u'1.4.2.2'), + StringStruct(u'FileVersion', u'1.5.0.0'), StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'), StringStruct(u'ProductName', u'本地解析服务'), - StringStruct(u'ProductVersion', u'1.4.2.2')]) + StringStruct(u'ProductVersion', u'1.5.0.0')]) ]), VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) ] diff --git a/TikTokLive.py b/TikTokLive.py deleted file mode 100644 index e4e4247f..00000000 --- a/TikTokLive.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:TikTokLive.py -@Date :2022/09/15 17:29:10 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -------------------------------------------------- -''' - -import Util - -cmd = Util.Command() - -live_url = Util.reFind(input('[ 📺 ]:输入抖音直播间web端链接,例如 https://live.douyin.com/176819813905:')) - -while live_url == '': - live_url = Util.reFind(input('[ 📺 ]:请输入正确的链接:')) - -Util.Lives(cmd).get_Live(live_url) \ No newline at end of file diff --git a/TikTokMultiGUI.py b/TikTokMultiGUI.py deleted file mode 100644 index 5cc85d0d..00000000 --- a/TikTokMultiGUI.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:GUI.py -@Date :2022/08/18 14:39:03 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/08/18 14:23:03 : Init -2022/08/18 14:39:03 : 添加多线程 -2022/08/18 14:39:03 : 添加控制台显示 -------------------------------------------------- -''' - -# Form implementation generated from reading ui file 'main.ui' -# -# Created by: PyQt5 UI code generator 5.15.4 -# -# WARNING: Any manual changes made to this file will be lost when pyuic5 is -# run again. Do not edit this file unless you know what you are doing. - -import sys - -from Util.Resource import * -from TikTokTool import Util - -from PyQt5 import QtCore, QtWidgets -from PyQt5.QtWidgets import QApplication -from PyQt5.QtCore import Qt, QPoint, QThread, QMutex -from PyQt5.QtCore import QObject, QEventLoop, QTimer -from PyQt5.QtGui import QMouseEvent, QTextCursor - -# 多线程保持Qt运行中不产生界面卡死 -class PreventFastClickThreadMutex(QThread): - qmut = QMutex() # 创建线程锁 - def __init__(self): - super().__init__() - - def run(self): - # 加锁 - self.qmut.lock() - # 获取命令行参数 - args = Util.Command().setting() - # 使用GUI的用户链接参数 - args[0] = newuid - # 获取主页消息 - profile = Util.Profile() - # 使用参数并下载 - profile.getProfile((args)) - # 解锁 - self.qmut.unlock() - -class Signal(QObject): - # 创建控制台内容更新信号 - text_update = QtCore.pyqtSignal(str) - - def write(self, text): - # 发送更新信号 - self.text_update.emit(str(text)) - # loop = QEventLoop() - # QTimer.singleShot(1, loop.quit) - # loop.exec_() - # QApplication.processEvents() - -class Ui_Dialog(QtWidgets.QMainWindow): - def __init__(self): - super().__init__() - self.setupUi(self) - sys.stdout = Signal() - # QApplication.processEvents() - # 实时显示输出, 将控制台的输出重定向到界面中 - sys.stdout.text_update.connect(self.updatetext) - - def updatetext(self, text): - """更新textBrowser - Args: - text : 控制台文本 - """ - cursor = self.textBrowser.textCursor() - cursor.movePosition(QTextCursor.End) - # QApplication.processEvents() - self.textBrowser.append(text) - self.textBrowser.setTextCursor(cursor) - self.textBrowser.ensureCursorVisible() - - def setupUi(self, Dialog): - Dialog.setObjectName("TikTokDownload") - Dialog.resize(1030, 600) - Dialog.setStyleSheet("") - self.Label_Left = QtWidgets.QLabel(Dialog) - self.Label_Left.setGeometry(QtCore.QRect(0, 0, 230, 600)) - self.Label_Left.setStyleSheet("background-color: #060716;\n" - "border-bottom-left-radius: 25px;\n" - "border-top-left-radius: 25px;\n" - "font: 10pt \"微软雅黑\";\n" - "color: #FFFFFF;") - self.Label_Left.setText("") - self.Label_Left.setObjectName("Label_Left") - self.Label_Right = QtWidgets.QLabel(Dialog) - self.Label_Right.setGeometry(QtCore.QRect(230, 0, 800, 600)) - self.Label_Right.setStyleSheet("background-color: rgb(255, 255, 255);\n" - "border-top-right-radius: 25px;\n" - "border-bottom-right-radius: 25px;") - self.Label_Right.setText("") - self.Label_Right.setObjectName("Label_Right") - self.Label_Logo = QtWidgets.QLabel(Dialog) - self.Label_Logo.setGeometry(QtCore.QRect(30, 50, 161, 41)) - self.Label_Logo.setStyleSheet( - "background: url(:/img/logo-horizontal.svg) no-repeat;") - self.Label_Logo.setText("") - self.Label_Logo.setObjectName("Label_Logo") - self.Label_Version = QtWidgets.QLabel(Dialog) - self.Label_Version.setGeometry(QtCore.QRect(180, 90, 54, 12)) - self.Label_Version.setStyleSheet("color: rgb(255, 255, 255);\n" - "font: 9pt \"微软雅黑\";") - self.Label_Version.setObjectName("Label_Version") - self.Button_Close = QtWidgets.QPushButton(Dialog) - self.Button_Close.setGeometry(QtCore.QRect(980, 20, 21, 21)) - self.Button_Close.setStyleSheet("border-radius: 10px;\n" - "background-color: rgb(255, 81, 53);") - self.Button_Close.setText("") - self.Button_Close.setObjectName("Button_Close") - self.Button_Max = QtWidgets.QPushButton(Dialog) - self.Button_Max.setGeometry(QtCore.QRect(950, 20, 21, 21)) - self.Button_Max.setStyleSheet("border-radius: 10px;\n" - "background-color: #FFC32D;") - self.Button_Max.setText("") - self.Button_Max.setObjectName("Button_Max") - self.Button_Min = QtWidgets.QPushButton(Dialog) - self.Button_Min.setGeometry(QtCore.QRect(920, 20, 21, 21)) - self.Button_Min.setStyleSheet("border-radius: 10px;\n" - "background-color: #37C847;") - self.Button_Min.setText("") - self.Button_Min.setObjectName("Button_Min") - self.plainTextEdit = QtWidgets.QPlainTextEdit(Dialog) - self.plainTextEdit.setGeometry(QtCore.QRect(260, 90, 601, 41)) - self.plainTextEdit.setAutoFillBackground(False) - self.plainTextEdit.setStyleSheet("background-color: #292B35;\n" - "border-radius: 10px;\n" - "font: 20pt \"微软雅黑\";\n" - "color: rgb(255, 255, 255);") - self.plainTextEdit.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.plainTextEdit.setVerticalScrollBarPolicy( - QtCore.Qt.ScrollBarAsNeeded) - self.plainTextEdit.setSizeAdjustPolicy( - QtWidgets.QAbstractScrollArea.AdjustIgnored) - self.plainTextEdit.setLineWrapMode( - QtWidgets.QPlainTextEdit.WidgetWidth) - self.plainTextEdit.setBackgroundVisible(False) - self.plainTextEdit.setCenterOnScroll(False) - self.plainTextEdit.setObjectName("plainTextEdit") - self.Button_Go = QtWidgets.QPushButton(Dialog) - self.Button_Go.setGeometry(QtCore.QRect(880, 90, 130, 41)) - self.Button_Go.setStyleSheet("#Button_Go {\n" - " border-radius: 10px;\n" - " font: 19pt \"微软雅黑\";\n" - " color: rgb(0, 0, 0);\n" - " background-color: #B9BAC7;\n" - "}\n" - "\n" - "#Button_Go:hover {\n" - " color:#F72C51;\n" - "}\n" - "\n" - "#Button_Go:pressed, QPushButton:checked {\n" - " background-color: #9d9d9d;\n" - "}\n" - "") - self.Button_Go.setObjectName("Button_Go") - self.Label_Background = QtWidgets.QLabel(Dialog) - self.Label_Background.setGeometry(QtCore.QRect(230, 0, 800, 60)) - self.Label_Background.setStyleSheet("background-color: rgb(199, 199, 199);\n" - "border-top-right-radius: 25px;") - self.Label_Background.setText("") - self.Label_Background.setObjectName("Label_Background") - self.widget = QtWidgets.QWidget(Dialog) - self.widget.setGeometry(QtCore.QRect(260, 190, 611, 351)) - self.widget.setObjectName("widget") - self.pushButton = QtWidgets.QPushButton(Dialog) - self.pushButton.setGeometry(QtCore.QRect(50, 390, 131, 61)) - self.pushButton.setStyleSheet("border-radius: 25px;\n" - "font: 16pt \"微软雅黑\";\n" - "color: rgb(255, 255, 255);\n" - "background-color: rgb(22, 23, 34);") - self.pushButton.setObjectName("pushButton") - self.pushButton_2 = QtWidgets.QPushButton(Dialog) - self.pushButton_2.setGeometry(QtCore.QRect(50, 490, 131, 61)) - self.pushButton_2.setStyleSheet("border-radius: 25px;\n" - "font: 16pt \"微软雅黑\";\n" - "color: rgb(255, 255, 255);\n" - "background-color: rgb(22, 23, 34);") - self.pushButton_2.setObjectName("pushButton_2") - self.Check_All = QtWidgets.QCheckBox(Dialog) - self.Check_All.setGeometry(QtCore.QRect(260, 140, 71, 16)) - self.Check_All.setObjectName("Check_All") - self.Check_All.setStyleSheet("font: 10pt \"微软雅黑\";\n") - self.Check_Cover = QtWidgets.QCheckBox(Dialog) - self.Check_Cover.setGeometry(QtCore.QRect(340, 140, 101, 16)) - self.Check_Cover.setObjectName("Check_Cover") - self.Check_Cover.setStyleSheet("font: 10pt \"微软雅黑\";\n") - self.Check_Music = QtWidgets.QCheckBox(Dialog) - self.Check_Music.setGeometry(QtCore.QRect(440, 140, 101, 16)) - self.Check_Music.setObjectName("Check_Music") - self.Check_Music.setStyleSheet("font: 10pt \"微软雅黑\";\n") - self.textBrowser = QtWidgets.QTextBrowser(Dialog) - self.textBrowser.setGeometry(QtCore.QRect(260, 170, 750, 401)) - self.textBrowser.setObjectName("textBrowser") - # 去除滚动条 - self.textBrowser.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.textBrowser.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.textBrowser.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) - # 去除边框 - self.textBrowser.setStyleSheet("border-width:0;border-style:outset;") - self.Label_Right.raise_() - self.Label_Background.raise_() - self.Label_Left.raise_() - self.Label_Logo.raise_() - self.Label_Version.raise_() - self.Button_Close.raise_() - self.Button_Max.raise_() - self.Button_Min.raise_() - self.plainTextEdit.raise_() - self.Button_Go.raise_() - self.widget.raise_() - self.Check_All.raise_() - self.pushButton.raise_() - self.pushButton_2.raise_() - self.Check_Cover.raise_() - self.Check_Music.raise_() - self.textBrowser.raise_() - self.setMinimumHeight(600) - self.setMinimumWidth(900) - self.retranslateUi(Dialog) - QtCore.QMetaObject.connectSlotsByName(Dialog) - - def mouseMoveEvent(self, e: QMouseEvent): - """# 重写移动事件 - - Args: - e (QMouseEvent): 鼠标事件 - """ - try: - self._endPos = e.pos() - self._startPos - self.move(self.pos() + self._endPos) - except: - pass - - def mousePressEvent(self, e: QMouseEvent): - try: - if e.button() == Qt.LeftButton: - self._isTracking = True - self._startPos = QPoint(e.x(), e.y()) - except: - pass - - def mouseReleaseEvent(self, e: QMouseEvent): - try: - if e.button() == Qt.LeftButton: - self._isTracking = False - self._startPos = None - self._endPos = None - except: - pass - - def btnClick(self): - """咻咻按钮事件 - """ - global newuid - newuid = self.plainTextEdit.toPlainText() - # 创建线程 - self.thread_1 = PreventFastClickThreadMutex() - # 开始线程 - self.thread_1.start() - - def retranslateUi(self, Dialog): - _translate = QtCore.QCoreApplication.translate - Dialog.setWindowTitle(_translate("TikTokDownload", "TikTokDownload")) - self.Label_Version.setText(_translate("TikTokDownload", "v2.1.1")) - self.Button_Min.setToolTip('最小化') - self.Button_Max.setToolTip('最大化') - self.Button_Close.setToolTip('关闭') - self.Button_Go.setText(_translate("TikTokDownload", "咻咻")) - self.Check_All.setText(_translate("TikTokDownload", "全部下载")) - self.pushButton.setText(_translate("TikTokDownload", "设置")) - self.pushButton_2.setText(_translate("TikTokDownload", "关于")) - self.Check_Cover.setText(_translate("TikTokDownload", "全部封面下载")) - self.Check_Music.setText(_translate("TikTokDownload", "全部配乐下载")) - self.plainTextEdit.setPlainText("https://v.douyin.com/efrHYf2/") - self.Button_Go.clicked.connect(lambda: self.btnClick()) - self.Button_Max.clicked.connect(lambda: self.MaxButton()) - self.Button_Min.clicked.connect(lambda: self.MinButton()) - self.Button_Close.clicked.connect(lambda: self.CloseButton()) - - def MaxButton(self): - """最大化与还原的切换 - """ - if self.isMaximized(): - self.showNormal() - self.Button_Max.setToolTip('最大化') - else: - self.showMaximized() - self.Button_Max.setToolTip('还原') - - def MinButton(self): - """最小化 - """ - self.showMinimized() - - def CloseButton(self): - """关闭 - """ - sys.exit(0) - -if __name__ == "__main__": - app = QApplication(sys.argv) - # 初始化控件 - w = Ui_Dialog() - # 去掉窗口标题栏和按钮 - w.setWindowFlags(Qt.FramelessWindowHint) - # 设置窗口真透明 - w.setAttribute(Qt.WA_TranslucentBackground) - w.show() - sys.exit(app.exec_()) diff --git a/TikTokTool.py b/TikTokTool.py index 5e41e929..ca1e0747 100644 --- a/TikTokTool.py +++ b/TikTokTool.py @@ -1,29 +1,51 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -''' -@Description:V1.py +""" +@Description:TikTokTool.py @Date :2022/07/29 23:19:14 @Author :JohnserfSeed -@version :1.0 +@version :1.5 @License :MIT License @Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com +@Mail :johnserf-seed@foxmail.com ------------------------------------------------- Change Log : 2022/07/29 23:19:14 : Init 2023/03/10 16:22:19 : gen dyheaders 2023/08/04 02:09:31 : async download +2023/12/26 18:01:56 : Switch to f2 ------------------------------------------------- -''' +""" -import Util +import sys +import time +from f2.cli.cli_console import RichConsoleManager as RCManager -if __name__ == '__main__': - # 获取命令行和配置文件 - cmd = Util.Command() - config = cmd.config_dict - dyheaders = cmd.dyheaders - # 异步下载作品 - Util.asyncio.run(Util.Profile(config, dyheaders).get_Profile()) - input("[ 提示 ]:下载完成,输入任意键退出。") \ No newline at end of file +if __name__ == "__main__": + RCManager = RCManager() + + if len(sys.argv) <= 1: + RCManager.rich_console.print( + "[bold red]请通过命令行启动并提供必要的参数, 输入[bold green] TikTokTool -h [/bold green]查看不同平台帮助。[/bold red]" + ) + from f2.utils import __version__ + + RCManager.rich_console.print( + f"[bold white]F2 Version:{__version__._version}[/bold white]" + ) + time.sleep(3) + sys.exit(1) + + from f2.apps.douyin.cli import douyin + from f2.apps.tiktok.cli import tiktok + + clis = [douyin, tiktok] + + selected = RCManager.rich_prompt.ask( + "[bold yellow]1.Douyin 2.TikTok:[/bold yellow]", + choices=[str(i) for i in range(1, len(clis) + 1)], + ) + + # 调用相应的 CLI 函数 + clis[int(selected) - 1]() diff --git a/TikTokUpdata.py b/TikTokUpdata.py deleted file mode 100644 index c2aae189..00000000 --- a/TikTokUpdata.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:TikTokUpdata.py -@Date :2022/11/30 15:58:51 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/11/30 : 通过版本文件检查是否有更新 -2023/08/04 : 完成了以下更新: - - 引入了 "update" 参数来决定是否每次进行版本更新 - - 自定义URL常量,方便修改 - - 使用 os.path.join() 代替手动拼接路径,以提高跨平台兼容性 - - 提取了进度条显示功能,简化了 zip_Download 方法 - - 优化了对用户输入的处理,改用循环重新询问,直到用户输入有效值为止 - - 使用 shutil.move() 和 shutil.rmtree() 代替 os.rename() 和 os.removedirs(),以解决部分情况下无法移动或删除文件的问题 -------------------------------------------------- -''' - -import os -import sys -import shutil -import zipfile -import requests - -from rich.console import Console -from rich.progress import Progress - -# 在文件顶部定义 URL 和其他常量,方便修改 -VERSION_URL = 'https://gitee.com/johnserfseed/TikTokDownload/raw/main/version' -ZIP_DOWNLOAD_URL = 'https://ghps.cc/https://github.com/Johnserf-Seed/TikTokDownload/archive/master.zip' -VERSION_FILE_NAME = 'version' -ZIP_FILE_NAME = 'TikTokDownload-main.zip' -EXTRACT_DIR_NAME = 'TikTokDownload-main' - - -class Updata: - def __init__(self, update: str) -> None: - # 使用rich打印输出 - self.console = Console() - # 检查更新参数 - if update.lower() != 'yes': - self.console.print('[ 🚩 ]:更新已被禁止') - return - - # 检查版本文件是否存在 - if os.path.exists(VERSION_FILE_NAME): - try: - with open(VERSION_FILE_NAME, 'r') as file: - version_str = file.read() - self.l_Version = int(version_str) - except: - self.console.print('[ 🌋 ]:获取本地版本号失败!') - self.zip_Download() # 如果获取本地版本号失败,则直接下载新版本 - return - else: - self.zip_Download() # 如果版本文件不存在,直接下载新版本 - return - - try: - self.console.print('[ 🗻 ]:获取最新版本号中!') - self.g_Version = int(requests.get(VERSION_URL).text) - except: - self.console.print('[ 🌋 ]:获取网络版本号失败!') - self.g_Version = self.l_Version - - self.get_Updata() - - def get_Updata(self): - while True: - if self.l_Version == self.g_Version: - self.console.print('[ 🚩 ]:目前 %i 版本已是最新' % self.l_Version) - return - elif self.l_Version < self.g_Version: - isUpdata = input('[ 🌋 ]:当前不是最新版本,需要升级吗? (y/n) :') - if isUpdata.lower() == 'y': - self.console.print('[ 🚩 ]:正在为你下载 %i 版本中,升级前请确保关闭所有打开的项目文件' % self.g_Version) - self.zip_Download() - return - elif isUpdata.lower() == 'n': - self.console.print('[ 🚩 ]:取消升级,旧版可能会出现没有修复的bug') - return - else: - self.console.print('[ 🌋 ]:无法识别的输入,请重新输入') - elif self.l_Version > self.g_Version: - self.console.print('[ 🚩 ]:本地版本异常,即将更新') - self.zip_Download() - return - - def zip_Download(self): - try: - response = requests.get(ZIP_DOWNLOAD_URL, stream=True) - response.raise_for_status() # 检查请求是否成功 - filesize = int(response.headers['content-length']) - except requests.RequestException: - self.console.print('[ 🚧 ]:下载文件失败,请检查网络连接并重试') - return - except KeyError: - self.console.print('[ 🚧 ]:获取文件大小失败,请检查网络连接并重试') - return - - with Progress() as progress: - task = progress.add_task("[cyan][ 下载 ]", total=filesize) - with open(ZIP_FILE_NAME, 'wb') as f: - for chunk in response.iter_content(chunk_size=512): - if not chunk: - break - f.write(chunk) - progress.update(task, advance=len(chunk)) - self.zip_Extract() - - def zip_Extract(self): - zip_file = zipfile.ZipFile(ZIP_FILE_NAME) - self.console.print('[ 提示 ]:开始解压缩升级包') - zip_file.extractall() - target = os.getcwd() - last = os.path.join(os.getcwd(), EXTRACT_DIR_NAME) - self.move_File(last, target) - - def move_File(self, oripath, tardir): - if not os.path.exists(oripath): - self.console.print('[ 🚩 ]:升级目录不存在,请重新运行') - status = 0 - else: - for i in os.listdir(oripath): - ori_file_path = os.path.join(oripath, i) - tar_file_path = os.path.join(tardir, i) - try: - self.console.print('[ 删除 ]:' + tar_file_path) - if os.path.isdir(tar_file_path): - shutil.rmtree(tar_file_path) - else: - os.remove(tar_file_path) - except Exception as e: - self.console.print(f'[ 异常 ]: {e}') - self.console.print('[ 移动 ]:' + ori_file_path) - self.console.print('[ 移到 ]:' + tar_file_path) - shutil.move(ori_file_path, tar_file_path) - self.console.print('[ 🚩 ]:删除更新临时目录') - with open('version', 'r') as file: - self.l_Version = int(file.read()) - shutil.rmtree(oripath) - status = 1 - return status - - -if __name__ == '__main__': - # 根据需要,向 Updata 实例传入 "yes" 或 "no" - Updata('yes') \ No newline at end of file diff --git a/Util/Check.py b/Util/Check.py deleted file mode 100644 index 44c3a48d..00000000 --- a/Util/Check.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Check.py -@Date :2022/08/16 18:34:27 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/08/16 18:34:27 : Init -------------------------------------------------- -''' - -import Util - -class Check(): - - - # 检测视频是否已经下载过 - def file_exists(self, save_dir: str, file_name: str, file_type: str) -> bool: - """ - 检测文件是否已经存在 - - Args: - save_dir (str): 保存的目录,应为绝对路径 - file_name (str): 文件名 - file_type (str): 文件类型 - - Return: - bool: 如果文件存在则返回True,否则返回False - """ - try: - # 验证输入 - if not all(isinstance(i, str) for i in [save_dir, file_name, file_type]): - Util.progress.console.print("[ 提示 ]: 所有参数必须是字符串。") - return False, None - - # 使用os.path.join()来进行路径拼接 - full_path = Util.os.path.join(save_dir, file_name) + file_type - # 检查文件是否存在 - return Util.os.path.isfile(full_path), full_path - - except Exception as e: - Util.progress.console.print(f"[ 异常 ]: {e}") - return False, None \ No newline at end of file diff --git a/Util/Command.py b/Util/Command.py deleted file mode 100644 index ea266c00..00000000 --- a/Util/Command.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Command.py -@Date :2022/07/30 18:38:09 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/07/30 18:38:09 : Init -2022/08/16 18:34:27 : change to class Command -2023/03/08 15:33:45 : add more arguments -------------------------------------------------- -''' - -import Util - -class Command: - - def __init__(self): - # 字典类型配置文件 - self.config_dict = {} - # 全局headers - self.dyheaders = { - 'Cookie': '', - 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36', - 'Referer':'https://www.douyin.com/' - } - self.setting() - - def argument(self): - """ - 获取命令行参数 - Returns: - args: 返回命令行对象 - """ - parser = Util.argparse.ArgumentParser( - description='TikTokTool V1.4.2.2 使用帮助') - parser.add_argument('--uid', '-u', type=str, - help='为用户主页链接,支持长短链', required=False) - parser.add_argument('--save','-s', type=str,help='视频保存目录,非必要参数, 默认Download', default='Download') - # parser.add_argument('--single', '-s', type=str, help='单条视频链接,非必要参数,与--user参数冲突') - parser.add_argument('--music', '-m', type=str, - help='是否下载视频原声, 默认no 可选yes', default='no') - parser.add_argument('--cover', type=str, - help='是否下载视频封面, 默认no 可选yes', default='no') - parser.add_argument('--desc', type=str, - help='是否保存视频文案, 默认no 可选yes', default='no') - parser.add_argument('--folderize', type=str, - help='是否将作品保存到单独的文件夹,默认yes 可选no', default='yes') - parser.add_argument('--mode', '-M', type=str, - help='下载模式选择,默认post 可选post|like|listcollection|wix', default='post') - parser.add_argument('--naming', type=str, - help='作品文件命名格式,默认为{create}_{desc}', default='{create}_{desc}') - parser.add_argument('--cookie', '-cookie', type=str, - help='大部分请求需要cookie,请调用扫码登录填写cookie', default='', required=False) - parser.add_argument('--interval', '-I', type=str, - help='根据作品发布日期区间下载作品,例如2022-01-01|2023-01-01', default='all') - parser.add_argument('--update', '-U', type=str, - help='选择是否自动升级,由于更新频率快,默认yes 可选no', default='yes') - parser.add_argument('--limit', type=str, - help='仅下载多少个视频,填all即是下载全部。实际比设置多3倍', default='all') - parser.add_argument('--max_connections', type=int, - help='网络请求的并发连接数,不宜设置过大', default=10) - parser.add_argument('--max_tasks', type=int, - help='异步的任务数,不宜设置过大', default=10) - - return parser.parse_args() - - def setting(self): - """ - 设置配置 - Returns: - dict: 返回字典类型配置文件 - """ - - # 检查配置文件是否存在 - cfg = Util.Config().check() - if cfg['cookie'] == '': - # sso登录 - login = Util.Login() - self.dyheaders = login.loginHeaders - else: - self.dyheaders['Cookie'] = cfg['cookie'] - - # 检测是否为命令行调用 - args = self.argument() - # 如果args中有任何非None的值则设置为命令行 - if len(Util.sys.argv) > 1: # 如果命令行参数列表的长度大于1,说明有提供命令行参数 - args = self.argument() - self.config_dict = vars(args) - Util.progress.console.print('[ 配置 ]:获取命令行完成!\r') - Util.log.info('[ 配置 ]:获取命令行完成!') - else: - self.config_dict = cfg - Util.progress.console.print('[ 配置 ]:读取本地配置完成!\r') - Util.log.info('[ 配置 ]:读取本地配置完成!') \ No newline at end of file diff --git a/Util/Config.py b/Util/Config.py deleted file mode 100644 index a626b95f..00000000 --- a/Util/Config.py +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Config.py -@Date :2022/07/29 23:18:47 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/07/29 23:18:47 : Init -2022/08/16 18:34:27 : Add log moudle -2023/03/08 14:42:05 : Add cookie,interval,update; conf -------------------------------------------------- -''' - -import Util - -class Config: - - def __init__(self): - self.default = { - 'uid': 'https://v.douyin.com/k9NXNcH/', - 'music': 'no', - 'cover': 'no', - 'desc': 'no', - 'path': 'Download', - 'folderize': 'yes', - 'mode': 'post', - 'naming': '{create}_{desc}', - 'cookie':'请扫码登录,cookie会自动保存', - 'interval':'all', - 'update':'yes', - 'limit': 'all', - 'max_connections': 10, - 'max_tasks': 10 - } - - def check(self): - """ - 检查配置文件,不存在就生成默认配置文件 - Returns: - self.cf: 配置文件对象 - """ - - if Util.os.path.isfile("conf.ini") == True: - # 用utf-8防止出错 - self.cf = Util.ConfigObj('conf.ini', encoding='utf-8') - else: - Util.progress.console.print('[ 提示 ]:没有检测到配置文件,生成中!\r') - Util.log.info('[ 提示 ]:没有检测到配置文件,生成中!') - try: - # 实例化读取配置文件 - self.cf = Util.ConfigObj('conf.ini', encoding='utf-8') - # 往配置文件写入内容 - self.cf['uid'] = 'https://v.douyin.com/JcjJ5Tq/' - # 添加注释 - self.cf.comments['uid'] = ['用户主页(非视频链接)', - '单视频请用TikTokDownload或TikTokWeb'] - self.cf['music'] = 'yes' - self.cf.comments['music'] = ['', - '视频原声保存(yes|no)'] - self.cf['cover'] = 'no' - self.cf.comments['cover'] = ['', - '视频封面保存(yes|no)'] - self.cf['desc'] = 'no' - self.cf.comments['desc'] = ['', - '视频文案保存(yes|no)'] - self.cf['path'] = 'Download' - self.cf.comments['path'] = ['', - '作品保存位置,只支持相对路径', - '不了解不用修改'] - self.cf['folderize'] = 'yes' - self.cf.comments['folderize'] = ['', - '作品保存到单独的文件夹(yes|no)', - '如果设置了保存原声、封面、文案的建议开启该选项'] - self.cf['mode'] = 'post' - self.cf.comments['mode'] = ['', - '下载模式(post|like|listcollection|wix合集暂未更新)', - '下载他人喜欢页、收藏视频请确保是开放所有人可见', - '如果是扫码登陆的下载自己的喜欢与收藏则无需设置所有人可见'] - self.cf['naming'] = '{create}_{desc}' - self.cf.comments['naming'] = ['', - '全局作品文件命名', - '{create}发布时间、{desc}作品文案、{id}作品id', - '只允许下划线_ 减号- 作为文件名间隔符', - '该配置会影响folderize中的作品文件夹命名'] - self.cf['cookie'] = '' - self.cf.comments['cookie'] = ['', - '置空该选项本程序会自动打开二维码获取cookie', - '本程序不会共享、存储、处理、加密、上传你的cookie配置', - '请妥善保管你个人的cookie信息,发issues贴log文件的时候注意脱敏,防止泄露!'] - self.cf['interval'] = 'all' - self.cf.comments['interval'] = ['', - '根据作品发布日期区间下载作品', - '例如2022-01-01|2023-01-01下载的是2022年所有作品', - '填all即是下载全部时间'] - self.cf['update'] = 'yes' - self.cf.comments['update'] = ['', - '自动更新(yes|no)', - '由于更新频率快,默认yes可以保持最新版本', - '关闭后仅提示有新版可下载'] - self.cf['max_connections'] = 10 - self.cf.comments['max_connections'] = ['', - '网络请求并发连接数', - '不宜设置过大,如遇错误可适当降低'] - self.cf['max_tasks'] = 10 - self.cf.comments['max_tasks'] = ['', - '异步的任务数', - '不宜设置过大,如遇错误可适当降低'] - - # 写入到文件 - self.cf.filename = 'conf.ini' - self.cf.write() - Util.progress.console.print('[ 配置 ]:配置文件生成成功!\r') - Util.log.info('[ 配置 ]:配置文件生成成功!') - - except Exception as writeiniError: - Util.progress.console.print('[ 配置 ]:配置文件写入失败!\r') - Util.log.error('[ 配置 ]:配置文件写入失败! %s' % writeiniError) - self.download() - - # 验证配置文件 - is_valid, message = validate_config(self.cf) - if is_valid: - # 是否自动升级 - Util.Updata(self.cf['update']) - Util.progress.console.print(message) - return self.cf - else: - Util.progress.console.print(message) - input('[ 配置 ]:按任意键退出。') - exit(0) - - def download(self) -> None: - """ - 下载配置文件 - """ - try: - Util.progress.console.print('[ 提示 ]:从GitHub为您下载配置文件!\r') - Util.log.info('[ 提示 ]:从GitHub为您下载配置文件!') - r = Util.requests.get( - 'https://cdn.jsdelivr.net/gh/Johnserf-Seed/TikTokDownload@main/conf.ini') - with open("conf.ini", "w") as f: - f.write(r.content) - Util.progress.console.print('[ 提示 ]:下载配置成功!\r') - Util.log.info('[ 提示 ]:下载配置成功!') - except Exception as iniError: - Util.progress.console.print('[ 提示 ]:下载失败,请检查网络!\r') - Util.log.info('[ 提示 ]:下载失败,请检查网络!') - Util.log.error(iniError) - - def save(self, cookie) -> None: - if Util.os.path.isfile("conf.ini") == True: - # 用utf-8防止出错 - self.cf = Util.ConfigObj('conf.ini', encoding='utf-8') - self.cf['cookie'] = cookie - # 写入到文件 - self.cf.filename = 'conf.ini' - self.cf.write() - Util.progress.console.print('[ 配置 ]:cookie更新成功!\r') - Util.log.info('[ 配置 ]:cookie更新成功!') - Util.log.info(cookie) - else: - self.check() - - return self.cf - -def validate_config(config): - """ - 验证配置文件的有效性 - - Args: - config (dict): 从配置文件读取的键值对。 - - Returns: - tuple: (bool, str)。如果配置有效,返回(True, "[ 配置 ]: 所有配置验证成功")。 - 如果配置无效,返回(False, 错误信息)。 - """ - - # 错误信息 - errors = [] - - # 验证 uid - uid = config.get('uid', '') - if not uid.startswith('https://') and not uid.startswith('http://'): - errors.append('[ 配置 ]:uid 不是一个有效的网络链接') - - # 验证 yes/no 设置 - for key in ['music', 'cover', 'desc', 'folderize', 'update']: - value = config.get(key, '').lower() - if value not in ['yes', 'no']: - errors.append(f'[ 配置 ]:{key} 应该为 "yes" 或 "no"') - - # 验证 path - path = config.get('path', '') - if path.startswith('/') or ':' in path: - errors.append('[ 配置 ]:path 只能是相对路径') - - # 验证 mode - mode = config.get('mode', '') - if mode not in ['post', 'like', 'listcollection', 'wix']: - errors.append('[ 配置 ]:mode 应为 "post"、"like"、"listcollection"或 "wix"之一') - - # 验证 naming - naming = config.get('naming', '') - if not any(tag in naming for tag in ['{create}', '{desc}', '{id}']): - errors.append('[ 配置 ]:naming 应至少包含 {create}、{desc} 或 {id} 中的一个。') - else: - stripped_naming = naming.replace('{create}', '').replace('{desc}', '').replace('{id}', '') - if any(ch for ch in stripped_naming if ch not in ('_', '-')): - errors.append('[ 配置 ]:naming 只允许下划线_ 减号- 作为文件名间隔符') - - # 验证 interval - interval = config.get('interval', '') - if interval != 'all' and '|' not in interval: - errors.append('[ 配置 ]:interval 应为 "all" 或使用"|"来间隔范围,如 "2022-01-01|2023-01-01"') - - # 验证 max_connections 和 max_tasks - for key in ['max_connections', 'max_tasks']: - value = str(config.get(key, '')) - if not value.isdigit(): - errors.append(f'[ 配置 ]:{key} 应该为数字') - - # 如果没有错误,则配置验证成功 - if not errors: - return True, "[ 配置 ]:配置验证成功!" - else: - return False, "\n".join(errors) \ No newline at end of file diff --git a/Util/Cookies.py b/Util/Cookies.py deleted file mode 100644 index 67b8dfe3..00000000 --- a/Util/Cookies.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Cookies.py -@Date :2023/07/29 20:13:24 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/07/29 20:13:24 - 添加不同类型的cookies生成 -2023/08/01 15:19:38 - 对response.cookies进行拆分 -------------------------------------------------- -''' - -import re -import time -import random -import requests - - -class Cookies: - - def __init__(self) -> None: - pass - - def generate_random_str(self, randomlength=16) -> str: - """ - 根据传入长度产生随机字符串 - param :randomlength - return:random_str - """ - random_str = '' - base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789=' - length = len(base_str) - 1 - for _ in range(randomlength): - random_str += base_str[random.randint(0, length)] - return random_str - - def generate_ttwid(self) -> str: - """ - 生成请求必带的ttwid - param :None - return:ttwid - """ - url = 'https://ttwid.bytedance.com/ttwid/union/register/' - data = '{"region":"cn","aid":1768,"needFid":false,"service":"www.ixigua.com","migrate_info":{"ticket":"","source":"node"},"cbUrlProtocol":"https","union":true}' - response = requests.request("POST", url, data=data) - # j = ttwid k = 1%7CfPx9ZM..... - for j, k in response.cookies.items(): - return k - - def get_fp(self) -> str: - """ - 生成verifyFp - - Returns: - str: verifyFp - """ - e = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - t = len(e) - milliseconds = int(round(time.time() * 1000)) - base36 = '' - while milliseconds > 0: - remainder = milliseconds % 36 - if remainder < 10: - base36 = str(remainder) + base36 - else: - base36 = chr(ord('a') + remainder - 10) + base36 - milliseconds = int(milliseconds / 36) - r = base36 - o = [''] * 36 - o[8] = o[13] = o[18] = o[23] = '_' - o[14] = '4' - - for i in range(36): - if not o[i]: - n = 0 or int(random.random() * t) - if i == 19: - n = 3 & n | 8 - o[i] = e[n] - ret = "verify_" + r + "_" + ''.join(o) - return ret - - def get_s_v_web_id(self) -> str: - """ - 生成s_v_web_id - - Returns: - str: s_v_web_id - """ - - e = list("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") - t = len(e) - n = self.base36_encode(int(time.time()*1000)) # 生成时间戳的36进制 - - r = [''] * 36 - r[8] = r[13] = r[18] = r[23] = "_" - r[14] = "4" - - for i in range(36): - if not r[i]: - o = int(random.random() * t) - r[i] = e[3 & o | 8 if i == 19 else o] - - return "verify_" + n + "_" + "".join(r) - - def base36_encode(self, number) -> str: - """ - 转换整数为base36字符串 - - Returns: - str: base36 string - """ - - alphabet = '0123456789abcdefghijklmnopqrstuvwxyz' - base36 = [] - - while number: - number, i = divmod(number, 36) - base36.append(alphabet[i]) - - return ''.join(reversed(base36)) - - def split_cookies(self, cookie_str:str) -> str: - """ - 拆分Set-Cookie字符串并拼接 - - Args: - cookie_str (str): _description_ - """ - - # 判断是否为字符串 - if not isinstance(cookie_str, str): - raise TypeError("cookie_str must be str") - - # 拆分Set-Cookie字符串,避免错误地在expires字段的值中分割字符串。 - cookies_list = re.split(', (?=[a-zA-Z])', cookie_str) - - # 拆分每个Cookie字符串,只获取第一个分段(即key=value部分) - cookies_list = [cookie.split(';')[0] for cookie in cookies_list] - - # 拼接所有的Cookie - cookie_str = ";".join(cookies_list) - - return cookie_str diff --git a/Util/Download.py b/Util/Download.py deleted file mode 100644 index e94c078a..00000000 --- a/Util/Download.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Download.py -@Date :2022/08/11 22:02:49 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/08/11 22:02:49 : Init -2022/08/30 00:30:09 : Add ImageDownload() -2023/08/04 00:48:30 : Add trim_filename(),download_file(),AwemeDownload() and Delete some unuseful function -------------------------------------------------- -''' - -import Util - -XB = Util.XBogus() -URLS = Util.Urls() - -class Download: - - def __init__(self, config): - # 配置文件 - self.config = config - # 文件检查是否存在 - self.check = Util.Check() - # 异步的任务数 - self.semaphore = Util.asyncio.Semaphore(int(self.config['max_tasks'])) - - def trim_filename(self, filename: str, max_length: int = 50) -> str: - """ - 裁剪文件名以适应控制台显示。 - - Args: - filename (str): 完整的文件名。 - max_length (int): 显示的最大字符数。 - - Returns: - str: 裁剪后的文件名。 - """ - - if len(filename) > max_length: - prefix_suffix_len = max_length // 2 - 2 # 确保前缀和后缀长度相等,并且在中间留有四个字符用于省略号("...") - return f"{filename[:prefix_suffix_len]}...{filename[-prefix_suffix_len:]}" - else: - return filename - - async def download_file(self, task_id: Util.TaskID, url: str, path: str) -> None: - """ - 下载指定 URL 的文件并将其保存在本地路径。 - - Args: - task_id (TaskID): 下载任务的唯一标识。 - url (str): 要下载的文件的 URL。 - path (str): 文件保存的本地路径. - """ - try: - async with self.semaphore: - connector = Util.aiohttp.TCPConnector(limit=int(self.config['max_connections'])) - - async with Util.aiohttp.ClientSession(connector=connector) as session: - async with session.get(url) as response: - if response.status != 200: - raise ValueError(f"HTTP连接意外: {response.status}") - - Util.progress.update(task_id, total=int(response.headers["Content-length"])) - with open(path, "wb") as dest_file: - Util.progress.start_task(task_id) - chunk_size = 32768 - while not Util.done_event.is_set(): - chunk = await response.content.read(chunk_size) - if not chunk: - break - dest_file.write(chunk) - Util.progress.update(task_id, advance=len(chunk)) - - except Util.aiohttp.ClientError as e: - Util.progress.console.print(f"[ 失败 ]:网络连接出错。异常:{e}") - except ValueError as e: - Util.progress.print(f"[ 失败 ]:该链接可能无法访问。 异常:{e}") - except FileNotFoundError: - Util.progress.print(f"[ 失败 ]:文件路径 {path} 无效或无法访问。") - except Exception as e: - Util.progress.print(f"[ 失败 ]:下载失败,未知错误。异常:{e}") - - async def AwemeDownload(self, aweme_data): - """ - 此函数用于从抖音作品数据列表(aweme_data)中异步下载音乐、视频和图集。 - 它会根据aweme_type的类型来决定是下载视频还是图集,并且会根据配置文件来判断是否需要下载音乐。 - 下载的文件会保存在用户目录下,并且会创建一个与抖音作品文案对应的子目录。 - 如果文件已经存在,作品将不会重新下载。 - - 文件的命名规范: - file_name: 文件名:ctime_f + desc + 文件类型。 例如'2021-02-15 18.09.05测试.mp4',用于控制台显示。 - file_path: 绝对路径的文件名:绝对路径 + ctime_f和作品同名目录 + file_name。例如'H:\\TikTokDownload\\Download\\post\\小e同学\\2021-02-15 18.09.05测试\\2021-02-15 18.09.05测试.mp4',用于文件保存。 - 作品文件夹的命名规范: ctime_f和作品同名目录。例如 2021-02-15 18.09.05测试 - - Args: - aweme_data (list): 抖音数据列表,列表的每个元素都是一个字典,字典包含了抖音的各种信息,如aweme_type, path, desc等。 - """ - - async def format_file_name(aweme: list, naming_template: str) -> str: - """ - 根据配置文件的全局格式化文件名。 - - Args: - - aweme (dict): 抖音数据的字典。 - - naming_template (str): 文件的命名模板,如 "{create}_{desc}"。 - - Returns: - - str: 格式化的文件名。 - """ - - # 使用给定的命名模板格式化文件名 - return naming_template.format(create=aweme['create_time'], desc=aweme['desc'], id=aweme['aweme_id']) - - async def initiate_desc(file_type: str, desc_content: str, file_suffix: str, base_path: str, file_name: str) -> None: - """ - 初始化文案保存。如果文案文件已经存在,则跳过保存。否则,直接将文案内容写入文件。 - - Args: - file_type (str): 文件类型描述,通常是"文案"。 - desc_content (str): 要保存的文案内容。 - base_path (str): 文案文件保存的基础目录路径。 - file_name (str): 文案文件的主要名称,不包含后缀。 - - Note: - 这个函数会检查文案文件是否已经在指定的路径存在。如果存在,跳过该任务。否则,将直接将文案内容写入文件。 - """ - - file_path = f'{file_name}{file_suffix}' - full_path = Util.os.path.join(base_path, file_path) - if Util.os.path.exists(full_path): - task_id = Util.progress.add_task(description=f"[ 跳过 ]:", - filename=self.trim_filename(file_path, 50), - total=1, completed=1) - Util.progress.update(task_id, completed=1) - else: - task_id = Util.progress.add_task(description=f"[ {file_type} ]:", - filename=self.trim_filename(file_path, 50), - start=False) - Util.progress.start_task(task_id) - with open(full_path, 'w', encoding='utf-8') as desc_file: - desc_file.write(desc_content) - # 更新进度条以显示任务完成 - Util.progress.update(task_id, completed=100) - - async def initiate_download(file_type: str, file_url: str, file_suffix: str, base_path: str, file_name: str) -> None: - """ - 初始化下载任务。如果文件已经存在,则跳过下载。否则,创建一个新的异步下载任务。 - - Args: - file_type (str): 文件类型描述,如“音乐”、“视频”或“封面”。 - file_url (str): 要下载的文件的URL。 - file_suffix (str): 文件的后缀名,如“.mp3”或“.mp4”。 - base_path (str): 文件保存的基础目录路径。 - file_name (str): 文件的主要名称,不包含后缀。 - - Note: - 这个函数会检查文件是否已经在指定的路径存在。如果存在,跳过该任务。否则,将创建一个新的下载任务。 - """ - - file_path = f'{file_name}{file_suffix}' - full_path = Util.os.path.join(base_path, file_path) - if Util.os.path.exists(full_path): - task_id = Util.progress.add_task(description=f"[ 跳过 ]:", - filename=self.trim_filename(file_path, 50), - total=1, completed=1) - Util.progress.update(task_id, completed=1) - else: - task_id = Util.progress.add_task(description=f"[ {file_type} ]:", - filename=self.trim_filename(file_path, 50), - start=False) - download_task = Util.asyncio.create_task(self.download_file(task_id, file_url, full_path)) - download_tasks.append(download_task) - # 这将使事件循环继续进行,允许任务立即开始 - await Util.asyncio.sleep(0) - - # 用于存储作者本页所有的下载任务, 最后会等待本页所有作品下载完成才结束本函数 - download_tasks = [] - - # 作品发布时间区间 - should_check_interval = False - start_date, end_date = (None, None) - if self.config['interval'] != 'all': - should_check_interval = True - start_str, end_str = self.config['interval'].split('|') - start_date = Util.time.strptime(start_str + " 00.00.00", '%Y-%m-%d %H.%M.%S') - end_date = Util.time.strptime(end_str + " 23.59.59", '%Y-%m-%d %H.%M.%S') - - # 遍历aweme_data中的每一个aweme字典 - for aweme in aweme_data: - aweme_time = Util.time.strptime(aweme['create_time'], '%Y-%m-%d %H.%M.%S') - # 如果设置了日期区间并且作品的发布日期不在指定的日期范围内,则跳过 - if should_check_interval: - # 如果 aweme_time 比不符合时间区间,跳过当前的作品 - if aweme_time < start_date or aweme_time > end_date: - continue - - # 如果设置了事件响应,则停止 - if Util.done_event.is_set(): - Util.progress.console.print("[ 提示 ]: 中断该页下载") - return - - # 获取文件的基础路径,这里的aweme['path']是到用户目录的绝对路径 - base_path = aweme['path'] - # 创建子目录名称 - subdir_name = await format_file_name(aweme, self.config['naming']) - # 如果folderize配置设置为'yes',则将作品单独保存为一个文件夹。 - if self.config['folderize'].lower() == 'yes': - desc_path = Util.os.path.join(base_path, subdir_name) - else: - desc_path = base_path # 直接使用基础路径,不创建子目录 - # 确保子目录存在,如果不存在,os.makedirs会自动创建 - Util.os.makedirs(desc_path, exist_ok=True) - - # 原声下载 - if self.config['music'].lower() == 'yes': - try: - music_url = aweme['music_play_url']['url_list'][0] - music_name = f"{await format_file_name(aweme, self.config['naming'])}_music" - await initiate_download("音乐", music_url, ".mp3", desc_path, music_name) - except Exception: - Util.progress.console.print("[ 失败 ]:该原声不可用,无法下载。") - Util.log.warning(f"[ 失败 ]:该原声不可用,无法下载。{aweme} 异常:{Exception}") - - # 视频下载 - if aweme['aweme_type'] == 0: - try: - video_url = aweme['video_url_list'][0] - video_name = f"{await format_file_name(aweme, self.config['naming'])}_video" - await initiate_download("视频", video_url, ".mp4", desc_path, video_name) - except Exception: - Util.progress.console.print("[ 失败 ]:该视频不可用,无法下载。") - Util.log.warning(f"[ 失败 ]:该视频不可用,无法下载。{aweme} 异常:{Exception}") - - # 封面下载 - if self.config['cover'].lower() == 'yes': - try: - cover_url = aweme['dynamic_cover'][0] - cover_name = f"{await format_file_name(aweme, self.config['naming'])}_cover" - await initiate_download("封面", cover_url, ".gif", desc_path, cover_name) - except Exception: - Util.progress.console.print(f"[ 失败 ]:该视频封面不可用,无法下载。") - Util.log.warning(f"[ 失败 ]:该视频封面不可用,无法下载。{aweme} 异常:{Exception}") - - # 图集下载 - elif aweme['aweme_type'] == 68: - try: - for i, image_dict in enumerate(aweme['images']): - image_url = image_dict.get('url_list', [None])[0] - image_name = f"{await format_file_name(aweme, self.config['naming'])}_image_{i + 1}" - await initiate_download("图集", image_url, ".jpg", desc_path, image_name) - except Exception: - Util.progress.console.print("[ 失败 ]:该图片不可用,无法下载。") - Util.log.warning(f"[ 失败 ]:该图片不可用,无法下载。{aweme} 异常:{Exception}") - - # 文案保存 - if self.config['desc'].lower() == 'yes': - try: - desc_name = f"{await format_file_name(aweme, self.config['naming'])}_desc" - await initiate_desc("文案", aweme['desc'], ".txt", desc_path, desc_name) - except Exception: - Util.progress.console.print(f"[ 失败 ]:保存文案失败。异常:{Exception}") - Util.log.warning(f"[ 失败 ]:保存文案失败。{aweme} 异常:{Exception}") - - # 等待本页所有的下载任务完成, 如果不等待的话就会还没等下完就去下载下一页了, 并发下载多了会被服务器断开连接 - await Util.asyncio.gather(*download_tasks) diff --git a/Util/Lives.py b/Util/Lives.py deleted file mode 100644 index f3ccbb6b..00000000 --- a/Util/Lives.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Live.py -@Date :2022/09/15 16:48:34 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -------------------------------------------------- -''' -import Util - -XB = Util.XBogus() -URLS = Util.Urls() - -class Lives: - - def __init__(self, cmd): - self.headers = cmd.dyheaders - - def get_Live(self, live_url:str) -> None: - """ - 获取直播信息 - - Args: - live_url (str): 直播间链接 - - Returns: - live_json (dict): 直播间信息 - """ - - pattern = r"https://live\.douyin\.com/(\d+)" - - match = Util.re.search(pattern, live_url) - if match: - web_rid = match.group(1) - - # 2023/02/06 https://live.douyin.com/webcast/room/web/enter/ - try: - live_api = f"{URLS.LIVE}{XB.getXBogus(f'aid=6383&device_platform=web&web_rid={web_rid}')[0]}" - except IndexError: - raise Exception('检查是否为直播链接\r') - - response = Util.requests.request("GET", live_api, headers=self.headers) - if response.text == '': - input('[ 🎦 ]:获取直播信息失败,请重新扫码登录\r') - exit(0) - - live_json = response.json() - - api_status_code = live_json.get("status_code") - if api_status_code == 4001038: - input('[ 📺 ]:该内容暂时无法无法查看,按回车退出') - exit(0) - - # 是否在播 - live_status = live_json.get("data").get("data")[0].get("status") - - if live_status == 4: - input('[ 📺 ]:当前直播已结束,按回车退出') - exit(0) - - # 直播标题 - title = live_json.get("data").get("data")[0].get("title") - - # 观看人数 - user_count = live_json.get("data").get("data")[0].get("user_count_str") - - # 昵称 - nickname = Util.replaceT(live_json.get("data").get("data")[0].get("owner").get("nickname")) - - # sec_uid - # sec_uid = live_json.get("data").get("data")[0].get("owner").get("sec_uid") - - # 直播间观看状态 - display_long = live_json.get("data").get("data")[0].get("room_view_stats").get("display_long") - - # 推流 - flv_pull_url = live_json.get("data").get("data")[0].get("stream_url").get("flv_pull_url") - - try: - # 分区 - partition = live_json.get("data").get("partition_road_map").get("partition").get("title") - sub_partition = live_json.get("data").get("partition_road_map").get("sub_partition").get("partition").get("title") - except Exception as e: - partition = '无' - sub_partition = '无' - - print(f'[ 💻 ]:直播间:{title} 当前{display_long} 主播:{nickname} 分区:{partition}--{sub_partition} 观看人数:{user_count}\r') - - flv = [] - print('[ 🎦 ]:直播间清晰度') - for i, f in enumerate(flv_pull_url.keys()): - print('[ %s ]: %s' % (i, f)) - flv.append(f) - - rate = int(input('[ 🎬 ]输入数字选择推流清晰度:')) - - # ld = 标清 - - # sd = 高清 - - # hd = 超清 - - # uhd = 蓝光 - - # or4 = 原画 - - # 显示清晰度列表 - print('[ %s ]:%s' % (flv[rate], flv_pull_url[flv[rate]])) - - input('[ 📺 ]:复制链接使用下载工具下载,按回车退出') diff --git a/Util/Log.py b/Util/Log.py deleted file mode 100644 index 3c3026cf..00000000 --- a/Util/Log.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Log.py -@Date :2022/08/17 00:10:38 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -------------------------------------------------- -''' - -import Util - -# 创建logs文件夹 -cur_path = Util.os.path.abspath( - Util.os.path.join(Util.os.path.dirname("__file__"))) -log_path = Util.os.path.join(cur_path, 'logs') -# 如果不存在这个logs文件夹,就自动创建一个 -if not Util.os.path.exists(log_path): - Util.os.mkdir(log_path) - - -class Log(object): - def __init__(self): - # 文件的命名 - self.logname = Util.os.path.join(log_path, '%s.log' % Util.time.strftime( - "%Y-%m-%d_%H%M%S", Util.time.localtime())) - Util.logging.basicConfig() - self.logger = Util.logging.getLogger("TikTokDownload") - self.logger.setLevel(Util.logging.INFO) - self.logger.propagate = False - # 日志输出格式 - self.formatter = Util.logging.Formatter( - '[%(asctime)s] - %(filename)s] - %(levelname)s: %(message)s') - - def __console(self, level, message): - """传入控制台日志 - - Args: - level (str): 日志等级 - message (str): 日志消息 - """ - # 创建一个FileHandler,用于写到本地 - fh = Util.logging.FileHandler( - self.logname, 'a', encoding='utf-8') # 这个是python3的 - fh.setLevel(Util.logging.INFO) - fh.setFormatter(self.formatter) - self.logger.addHandler(fh) - - # 创建一个StreamHandler,用于输出到控制台 - ch = Util.logging.StreamHandler() - ch.setLevel(Util.logging.ERROR) - ch.setFormatter(self.formatter) - self.logger.addHandler(ch) - - if level == 'info': - self.logger.info(message) - elif level == 'debug': - self.logger.debug(message) - elif level == 'warning': - self.logger.warning(message) - elif level == 'error': - self.logger.error(message) - # 这两行代码是为了避免日志输出重复问题 - self.logger.removeHandler(ch) - self.logger.removeHandler(fh) - # 关闭打开的文件 - fh.close() - - def debug(self, message): - self.__console('debug', message) - - def info(self, message): - self.__console('info', message) - - def warning(self, message): - self.__console('warning', message) - - def error(self, message): - self.__console('error', message) diff --git a/Util/Login.py b/Util/Login.py deleted file mode 100644 index bbac5f18..00000000 --- a/Util/Login.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Login.py -@Date :2023/08/01 16:29:46 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/08/01 16:29:46 - 添加登录日志消息映射 -------------------------------------------------- -''' - -import Util - -XB = Util.XBogus() -URLS = Util.Urls() - -class Login(): - - def __init__(self) -> None: - # 登录日志消息映射 - self.status_mapping = { - '1': {"message": '[ 登录 ]:等待二维码扫描!\r', "log": Util.log.info}, - '2': {"message": '[ 登录 ]:扫描二维码成功!\r', "log": Util.log.info}, - '3': {"message": '[ 登录 ]:确认二维码登录!\r', "log": Util.log.info}, - '4': {"message": '[ 登录 ]:访问频繁,请检查参数!\r', "log": Util.log.warning}, - '5': {"message": '[ 登录 ]:二维码过期,重新获取!\r', "log": Util.log.warning} - } - - self.verifyFp = '' - - self.loginHeaders = { - 'Cookie': f'ttwid={Util.Cookies().generate_ttwid()}', - 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183', - 'Referer':'https://www.douyin.com/' - } - - # 初始化默认调用登录二维码 - self.get_qrcode() - - - def get_qrcode(self) -> str: - """ - 获取登录二维码 - - Raises: - RuntimeError: 网络异常: 获取二维码失败 - - Returns: - str: token - """ - - verifyFp = Util.Cookies().get_fp() - self.verifyFp = verifyFp - - params = XB.getXBogus(f'service=https%3A%2F%2Fwww.douyin.com&need_logo=false&need_short_url=true&device_platform=web_app&aid=6383&account_sdk_source=sso&sdk_version=2.2.5&language=zh&verifyFp={self.verifyFp}&fp={self.verifyFp}') - domain = URLS.SSO_LOGIN_GET_QR - - try: - response = Util.requests.get(domain + params[0], headers=self.loginHeaders) - response.raise_for_status() - data = response.json() - qrcode_url = data.get('data', {}).get('qrcode_index_url', '') - token = data.get('data', {}).get('token', '') - self.show_qrcode(qrcode_url) - self.check_qrconnect(token) - except Util.requests.exceptions.RequestException as e: - if response: - error_message = f"网络异常: 获取二维码失败。 状态码: {response.status_code}, 响应体: {response.text}, 异常: {e}" - else: - error_message = f"网络异常: 获取二维码失败。 无法连接到服务器。 异常: {e}" - Util.log.error(error_message) - raise RuntimeError(error_message) from e - - - def check_qrconnect(self, token) -> bool: - """ - 检查二维码状态 - - Args: - token (str): 登录二维码token - - Raises: - RuntimeError: 网络异常: 检查二维码连接失败 - - Return: - bool: 是否登录成功 - """ - - params = XB.getXBogus(f'token={token}&service=https%3A%2F%2Fwww.douyin.com&need_logo=false&need_short_url=true&device_platform=web_app&aid=6383&account_sdk_source=sso&sdk_version=2.2.5&language=zh&verifyFp={self.verifyFp}&fp={self.verifyFp}') - domain = URLS.SSO_LOGIN_CHECK_QR - - try: - while True: - response = Util.requests.get(domain + params[0], headers=self.loginHeaders) - response.raise_for_status() - data = response.json().get('data', {}) - status = data.get('status', '') - match status: - case '1': - self.log_and_print('1') - case '2': - self.log_and_print('2') - case '3': - self.log_and_print('3') - redirect_url = data.get('redirect_url', '') - login_cookies = Util.Cookies().split_cookies(response.headers.get('set-cookie', '')) - return self.login_redirect(redirect_url, login_cookies) - case '4': - self.log_and_print('4') - case '5': - self.log_and_print('5') - self.get_qrcode() - break - - Util.time.sleep(3) - except Util.requests.exceptions.RequestException as e: - if response: - error_message = f"网络异常: 检查二维码扫码状态失败。 状态码: {response.status_code}, 响应体: {response.text}, 异常: {e}" - else: - error_message = f"网络异常: 获取二维码失败。 无法连接到服务器。 异常: {e}" - Util.log.error(error_message) - raise RuntimeError(error_message) from e - - - def login_redirect(self, redirect_url, cookie) -> bool: - """ - 登录重定向 - - Args: - redirect_url (str): 重定向链接 - cookie (str): 登录时的Cookie值 - """ - - self.loginHeaders['Cookie'] = cookie - login_response = Util.requests.get(redirect_url, headers=self.loginHeaders) - - if login_response.history[0].status_code == 302: - # 获取重定向链接中的Cookie并更新 - self.loginHeaders['Cookie'] = Util.Cookies().split_cookies(login_response.history[1].headers.get('set-cookie', '')) - # 绑定XB算法的UA - self.loginHeaders['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' - # 写入配置文件 - Util.Config().save(self.loginHeaders['Cookie']) - Util.progress.console.print('[ 登录 ]:重定向登录成功\r') - Util.log.info('[ 登录 ]:重定向登录成功') - return True - else: - Util.progress.console.print('[ 登录 ]:重定向登录失败\r') - if login_response: - error_message = f"网络异常: 重定向登录失败。 状态码: {login_response.status_code}, 响应体: {login_response.text}" - else: - error_message = f"网络异常: 重定向登录失败。 无法连接到服务器。" - Util.log.warning(error_message) - return False - - - def show_qrcode(self, qrcode_url) -> None: - """ - 显示二维码 - - Args: - qrcode_url (str): 登录二维码链接 - """ - - # # 创建QR码图像 - # qr_code_img = Util.qrcode.make(qrcode_url) - - # # 显示QR码图像 - # qr_code_img.show() - - qr = Util.qrcode.QRCode() - - # 添加数据 - qr.add_data(qrcode_url) - - # 生成二维码 - qr.make(fit=True) - - # 在控制台以 ASCII 形式打印二维码 - qr.print_ascii(invert=True) - - Util.progress.console.print("[ 登录 ]:请扫描登录二维码。\r") - Util.log.info("[ 登录 ]:请扫描登录二维码。") - - - def log_and_print(self, status): - """ - 输出日志和控制台消息 - - Args: - status (str): 根据status来查找错误信息 - """ - data = self.status_mapping.get(status, {}) - message = data.get("message", "") - log_func = data.get("log", Util.log.info) - Util.progress.console.print(message) - log_func(message) diff --git a/Util/NickMapper.py b/Util/NickMapper.py deleted file mode 100644 index abeb9204..00000000 --- a/Util/NickMapper.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:NickMapper.py -@Date :2023/07/17 16:56:50 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/07/17 16:56:50 - 初始化用户昵称与作品映射 -------------------------------------------------- -''' - -import sqlite3 - -class NickMapper: - def __init__(self, db_name: str) -> None: - """ - 初始化 NickMapper 对象 - - Args: - db_name (str): 昵称映射表的数据库名称 - """ - self.db_name = db_name - self.conn = None - - def connect(self) -> None: - """ - 连接到数据库并创建昵称映射表 - """ - self.conn = sqlite3.connect(self.db_name) - self._create_table() - - def _create_table(self) -> None: - """ - 在数据库中创建昵称映射表 - """ - c = self.conn.cursor() - c.execute('''CREATE TABLE IF NOT EXISTS nickname_mapping - (sec_user_id TEXT PRIMARY KEY, nickname TEXT)''') - - def add_mapping(self, sec_user_id: str, nickname: str) -> None: - """ - 添加昵称映射 - - Args: - sec_user_id (str): 用户唯一标识 - nickname (str): 可变昵称 - """ - c = self.conn.cursor() - - # 检查用户 ID 是否已经存在 - c.execute('SELECT * FROM nickname_mapping WHERE sec_user_id=?', (sec_user_id,)) - result = c.fetchone() - - if result: - # 用户 ID 已存在,更新昵称映射 - c.execute('UPDATE nickname_mapping SET nickname=? WHERE sec_user_id=?', (nickname, sec_user_id)) - else: - # 用户 ID 不存在,插入新的昵称映射 - c.execute('INSERT INTO nickname_mapping VALUES (?, ?)', (sec_user_id, nickname)) - - self.conn.commit() - - def update_mapping(self, sec_user_id: str, new_nickname: str) -> None: - """ - 更新昵称映射 - - Args: - sec_user_id (str): 用户唯一标识 - new_nickname (str): 新的可变昵称 - """ - c = self.conn.cursor() - - # 检查用户 ID 是否已经存在 - c.execute('SELECT * FROM nickname_mapping WHERE sec_user_id=?', (sec_user_id,)) - result = c.fetchone() - - if result: - # 用户 ID 存在,更新昵称映射 - c.execute('UPDATE nickname_mapping SET nickname=? WHERE sec_user_id=?', (new_nickname, sec_user_id)) - else: - # 用户 ID 不存在,可抛出异常 - raise ValueError(f"User ID '{sec_user_id}' does not exist in the nickname mapping table.") - - self.conn.commit() - - def get_nickname(self, sec_user_id: str) -> str: - """ - 获取昵称映射 - - Args: - sec_user_id (str): 用户唯一标识 - - Returns: - str: 对应的可变昵称,如果不存在则返回 None - """ - c = self.conn.cursor() - c.execute('SELECT nickname FROM nickname_mapping WHERE sec_user_id=?', (sec_user_id,)) - result = c.fetchone() - return result[0] if result else None - - def delete_mapping(self, sec_user_id: str) -> None: - """ - 删除昵称映射 - - Args: - sec_user_id (str): 用户唯一标识 - """ - c = self.conn.cursor() - c.execute('DELETE FROM nickname_mapping WHERE sec_user_id=?', (sec_user_id,)) - self.conn.commit() - - def close(self) -> None: - """ - 关闭与数据库的连接 - """ - if self.conn: - self.conn.close() diff --git a/Util/Profile.py b/Util/Profile.py deleted file mode 100644 index 9206feba..00000000 --- a/Util/Profile.py +++ /dev/null @@ -1,511 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Profile.py -@Date :2022/08/11 23:13:22 -@Author :JohnserfSeed -@version :1.0 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/08/11 23:13:22 : Init -2023/08/17 17:46:08 : Fixes where downloads would be skipped when has_more was 0. -2023/08/17 17:46:08 : Added a signal that can interrupt the download. -------------------------------------------------- -''' - -import Util - - - -XB = Util.XBogus() -URLS = Util.Urls() - - -class Profile: - - def __init__(self, config, dyheaders): - # 抓获所有视频 - self.Isend = False - # 第一次访问页码 - self.max_cursor = 0 - # 全局IOS头部 - self.headers = dyheaders - # 配置文件 - self.config = config - # 记录配置文件 - Util.log.info(f"配置文件:{config}") - # 昵称映射表 - self.nick_mapper = Util.NickMapper('nickname_mapping.db') - # 连接数据库 - self.nick_mapper.connect() - # 创建下载实例 - self.download = Util.Download(self.config) - - def create_user_folder(self, config: dict, nickname: Util.Union[str, int]) -> None: - """ - 根据提供的配置文件和昵称,创建对应的保存目录。 - 如果未在配置文件中指定路径,则默认为 "Download"。 - 仅支持相对路径。 - - Args: - config (dict): 配置文件,字典格式。 - nickname (Union[str, int]): 用户的昵称,允许字符串或整数。 - Raises: - TypeError: 如果 config 不是字典格式,将引发 TypeError。 - """ - - # 确定函数参数是否正确 - if not isinstance(config, dict): - raise TypeError("config 参数必须是字典。") - - # 获取相对路径 - path = Util.os.path.join(".", config.get('path', 'Download'), config['mode'], nickname) - - # 获取绝对路径 - path = Util.os.path.abspath(path) - if not Util.os.path.exists(path): - # 创建用户文件夹 - Util.os.makedirs(path) - - return path - - async def re_match(self, session: Util.aiohttp.ClientSession, inputs: str) -> Util.Optional[Util.re.Match]: - """ - 根据传入的url,正则匹配sec_user_id - - Args: - session (aiohttp.ClientSession): HTTP session - inputs (str): 单条url - Return: - match (re.Match): 匹配到的结果 - """ - - match = None - - async with session.get(url=Util.reFind(inputs), - timeout=10, - allow_redirects=True) as response: - # 检查响应状态码,如果不是200和444,会抛出异常 - if response.status in {200, 444}: - if 'v.douyin.com' in inputs: - pattern = r"sec_uid=([^&]*)" - match = Util.re.search(pattern, response.url.path_qs) - else: - pattern = r"user/([^/?]*)" - match = Util.re.search(pattern, response.url.path_qs) - - return match - - async def get_request_data(self, method: str, url: str, headers: dict, data: dict = None): - """ - 发送异步HTTP请求并获取返回的接口数据 - - Args: - method (str): HTTP请求的方法,如'GET', 'POST'等 - url (str): 需要发送请求的URL - headers (dict): HTTP请求的头部 - data (dict, optional): 如果请求方法为'GET',需要发送的数据。默认为None。 - - Returns: - Tuple[List[dict], int, bool]: 返回一个元组,包含三个元素: - 1. 一个字典列表,每个字典代表一页作品信息 - 2. 一个整数,表示下次请求的页码 - 3. 一个布尔值,表示是否有更多作品 - """ - - async with Util.aiohttp.ClientSession() as session: - async with session.request(method, url, headers=headers, data=data, timeout=10) as response: - if response.status == 200: - if response.text != '': - api_data = await response.json() - info_status_code = api_data.get("status_code", None) - # 确保接口返回数据正常 - if info_status_code == 0: - # 接口相关(逆天,收藏接口完全不一样) - if method == "POST": - max_cursor = api_data.get("cursor", 0) - else: - max_cursor = api_data.get("max_cursor", 0) - has_more = api_data.get("has_more") - aweme_list = api_data.get("aweme_list", []) - return aweme_list, max_cursor, has_more - else: - raise RuntimeError(f"接口返回异常: status_code={info_status_code}") - else: - raise RuntimeError('获取接口数据失败,请从删除配置文件中的cookie,重新扫码登录并检查是否触发人机验证\r') - else: - raise Util.aiohttp.ClientError(f'本地网络错误 status_code={response.status}') - - async def get_all_sec_user_id(self, inputs: Util.Union[str, list]) -> Util.Union[str, list]: - """ - 获取用户SECUID,传入单条url或者列表url都可以解析出sec_user_id。 - - Args: - inputs (Union[str, list]): 单条url或者列表url - Return: - sec_user_id (Union[str, list]): 用户的唯一标识,返回字符串或列表 - """ - - # 进行参数检查 - if not isinstance(inputs, (str, list)): - raise TypeError("输入参数必须是字符串或列表。") - - # 从字符串提取 - if isinstance(inputs, str): - try: - async with Util.aiohttp.ClientSession() as session: - match = await self.re_match(session, inputs) - if match: - return match.group(1) - else: - raise ValueError("链接错误,无法提取用户ID.") - except Util.aiohttp.ClientError as e: - raise RuntimeError(f"网络连接异常,异常:{e}") - - # 从列表提取 - elif isinstance(inputs, list): - try: - # 处理列表 - sec_user_id_list = [] - async with Util.aiohttp.ClientSession() as session: - tasks = [] - for url in inputs: - task = Util.asyncio.ensure_future(self.re_match(session, url)) - tasks.append(task) - responses = await Util.asyncio.gather(*tasks) - for match in responses: - if match: - sec_user_id_list.append(match.group(1)) - else: - raise ValueError("链接错误,无法提取用户ID.") - except ValueError: - raise ValueError("列表url非字符串。") - except Util.aiohttp.ClientError as e: - raise RuntimeError(f"网络连接异常,异常:{e}") - - return sec_user_id_list - - async def get_diff_type_url(self, config: dict, sec_user_id: Util.Union[str, None], count = 20, cursor = 0) -> str: - """ - 根据传入配置文件中的mode和用户sec_user_id,生成不同作品类型的接口链接。 - - Args: - config (dict): 字典配置文件 - sec_user_id (str): 用户唯一标识 - count (int): 作品数 - cursor (long): 作品页码 - Return: - domain + params[0] (str): 拼接接口链接 - """ - - # 确定函数参数是否正确 - if not isinstance(config, dict): - raise TypeError("config 参数必须是字典.") - if not isinstance(sec_user_id, str): - raise TypeError("sec_user_id 参数必须是字符串.") - - # 生成接口链接,收藏夹的接口麻烦点的 - mode = config.get('mode', 'post').lower() - if mode == "post" and sec_user_id is not None: - params = XB.getXBogus(f'aid=6383&sec_user_id={sec_user_id}&count={count}&max_cursor={cursor}&cookie_enabled=true&platform=PC&downlink=10') - domain = URLS.USER_POST - self.type_data = None - elif mode == "like" and sec_user_id is not None: - params = XB.getXBogus(f'aid=6383&sec_user_id={sec_user_id}&count={count}&max_cursor={cursor}&cookie_enabled=true&platform=PC&downlink=10') - domain = URLS.USER_FAVORITE_A - self.type_data = None - elif mode == "listcollection": - params = XB.getXBogus(f'aid=6383&cookie_enabled=true&platform=PC&downlink=1.5') - domain = URLS.USER_COLLECTION - self.type_data = f'count={count}&cursor={cursor}' - - return domain + params[0] - - async def get_user_base_info(self, headers: dict, sec_user_id: Util.Union[str, list]) -> dict: - """ - 根据 sec_user_id 来获取用户im基本数据 - - Args: - headers (dict): 包含 Cookie、User-Agent、Referer 等请求头信息 - sec_user_id (Union[str, list]): 用户的唯一标识,可以是字符串或列表 - Return: - data (dict): 返回该用户的基本信息 - """ - - # 确定函数参数是否正确 - if not isinstance(headers, dict): - raise TypeError("headers 参数必须是字典.") - if not isinstance(sec_user_id, (str, list)): - raise TypeError("sec_user_id 参数必须是字符串或列表.") - - params = XB.getXBogus("aid=6383&platform=PC&downlink=1.25") - domain = URLS.USER_SHORT_INFO - - # 该接口的参数是列表 - if isinstance(sec_user_id, str): - sec_user_id_json = Util.json.dumps([sec_user_id]) - else: - sec_user_id_json = Util.json.dumps(sec_user_id) - - request_data = 'sec_user_ids=%s' % Util.parse.quote(str(sec_user_id_json)) - data = {} - - try: - async with Util.aiohttp.ClientSession() as session: - async with session.get(url=domain + params[0], - headers=headers, - data=request_data, - proxy=None, timeout=10) as response: - if response.status == 200: - data = await response.json() - info_status_code = data.get("status_code", None) - # 确保接口返回数据正常 - """ - info_status_code == 0 说明接口返回正常 - info_status_code == 5 说明接口参数异常 - info_status_code == 8 说明用户未登录 - """ - if info_status_code == 0: - data = data.get("data", {}) - else: - raise RuntimeError(f"接口返回异常: {info_status_code}") - except Util.aiohttp.ClientError as e: - raise RuntimeError(f"请求异常: {str(e)}") - - return data - - async def get_user_profile_info(self, headers: dict, sec_user_id: str) -> dict: - """ - 根据 sec_user_id 来获取用户完整信息 - - Args: - headers (dict): 包含 Cookie、User-Agent、Referer 等请求头信息 - sec_user_id (str): 用户的唯一标识,可以是字符串或列表 - Return: - data (dict): 返回该用户的完整信息 - """ - - # 确定函数参数是否正确 - if not isinstance(headers, dict): - raise TypeError("headers 参数必须是字典.") - if not isinstance(sec_user_id, str): - raise TypeError("sec_user_id 参数必须是字符串.") - - params = XB.getXBogus(f"device_platform=webapp&aid=6383&sec_user_id={sec_user_id}&cookie_enabled=true&platform=PC&downlink=10") - domain = URLS.USER_DETAIL - - data = {} - - try: - async with Util.aiohttp.ClientSession() as session: - async with session.get(url=domain + params[0], - headers=headers, - proxy=None, timeout=10) as response: - if response.status == 200: - if response.text != '': - data = await response.json() - info_status_code = data.get("status_code", None) - # 确保接口返回数据正常 - if info_status_code == 0: - data = data.get("user", {}) - else: - raise RuntimeError(f"接口内容返回异常: status_code={info_status_code}") - else: - raise RuntimeError("接口返回空,检查cookie是否过期以及是否出现人机验证,解决不了请重新扫码登录\r") - else: - raise RuntimeError(f"请检查网络状况。 状态码: {response.status}, 响应体: {response.text}") - except Util.aiohttp.ClientError as e: - raise RuntimeError(f"本地网络请求异常。 异常: {e}\r") from e - - return data - - async def get_user_post_info(self, headers: dict, url: str) -> dict: - """ - 获取指定用户的作品信息 - - Args: - headers (dict): HTTP请求的头部 - url (str): 需要发送请求的URL - - Returns: - List[dict]: 返回一个字典列表,每个字典包含一个作品的所有信息,如作品类型,作品ID,作品描述,作者信息,音乐信息等 - """ - - aweme_data = [] - - try: - if self.config['mode'] != 'listcollection': - aweme_list, max_cursor, has_more = await self.get_request_data('GET', url, headers) - else: - headers['Content-Type'] = 'application/x-www-form-urlencoded' - aweme_list, max_cursor, has_more = await self.get_request_data('POST', url, headers, self.type_data) - except Util.aiohttp.ClientError as e: - raise RuntimeError(f"本地请求异常, 异常: {e}") from e - except Exception as e: - raise RuntimeError(f"运行异常, 异常: {e}") from e - - if aweme_list == []: - data = {} - # 作品相关 - data['max_cursor'] = max_cursor - data['has_more'] = has_more - aweme_data.append(data) - else: - for item in aweme_list: - data = {} - # 类别相关 - author = item.get("author", {}) - music = item.get("music", {}) - video = item.get("video", {}) - aweme_type = item.get("aweme_type", None) - - if aweme_type == 0: - # 视频相关 - bit_rate = video.get("bit_rate", []) - # 封面相关 - cover = video.get("cover", {}) - dynamic_cover = video.get("dynamic_cover", {}) - try: - data['video_uri'] = bit_rate[0].get("play_addr", {}).get("uri", None) - data['video_url_list'] = bit_rate[0].get("play_addr", {}).get("url_list", []) - data['cover'] = cover.get("url_list",[]) - data['dynamic_cover'] = dynamic_cover.get("url_list",[]) - except IndexError: - # raise RuntimeError("该视频已被下架,无法下载。") - continue - - elif aweme_type == 68: - # 图集相关 - data['images'] = item.get("images", []) - data['cover'] = '' - data['dynamic_cover'] = '' - - # 作品相关 - data['max_cursor'] = max_cursor - data['has_more'] = has_more - data['aweme_type'] = aweme_type - data['aweme_id'] = item.get("aweme_id", None) - data['desc'] = Util.replaceT(item.get("desc", None)) - # 将UNIX时间戳转换为格式化的字符串 - data['create_time'] = Util.time.strftime('%Y-%m-%d %H.%M.%S', Util.time.localtime(item.get("create_time", None))) - - # 作者相关 - data['uid'] = author.get("uid", None) - # 判断昵称映射表中是否已经存在该用户 - if self.nick_mapper.get_nickname(author['sec_uid']) is None: - # 如果不存在,添加昵称映射 - self.nick_mapper.add_mapping(author['sec_uid'], author['nickname']) - # 关闭索引 - # self.nick_mapper.close() - # 获取昵称映射 - data['nickname'] = self.nick_mapper.get_nickname(author['sec_uid']) - data['aweme_count'] = author.get("aweme_count", None) - - # 原声相关 - data['music_title'] = music.get("title", None) - data['music_play_url'] = music.get("play_url", None) - - # 保存路径相关 - data['path'] = self.path - - aweme_data.append(data) - - return aweme_data - - async def process_aweme_data(self, aweme_data): - """ - 处理 aweme_data,执行下载等操作。 - - Args: - aweme_data - """ - if 'aweme_id' not in aweme_data[0]: - # 如果数据为空,直接返回 - Util.progress.console.print(f'[ 提示 ]:抓获{self.max_cursor}页数据为空,已跳过。\r') - Util.log.info(f'[ 提示 ]:抓获{self.max_cursor}页数据为空,已跳过。') - return - # 下载作品 - with Util.progress: - await self.download.AwemeDownload(aweme_data) - Util.progress.console.print(f'[ 提示 ]:抓获{self.max_cursor}页数据成功! 该页共{len(aweme_data)}个作品。\r') - Util.log.info(f'[ 提示 ]:抓获{self.max_cursor}页数据成功! 该页共{len(aweme_data)}个作品。') - - async def get_Profile(self, count: int = 20) -> None: - """ - 获取用户的Profile并设置相应的实例变量。 - - 首先获取用户的唯一标识和昵称,然后根据 mode 和其他配置来生成 profile_URL,并创建用户的文件夹。 - 如果 mode 是 'listcollection',则 params 将不包含 sec_user_id,否则包含 sec_user_id。 - 生成的 profile_URL 将用于后续的数据获取,最后保存用户的主页链接。 - - Raises: - Exception: 如果在获取用户信息过程中出现错误,则会抛出异常。 - """ - - try: - # 获取sec_user_id - self.sec_user_id = await self.get_all_sec_user_id(inputs=self.config['uid']) - - # 用户详细信息 - user_profile_info = await self.get_user_profile_info(self.headers, self.sec_user_id) - - # 用户昵称,需要替换非法字符防止因为昵称字符问题导致报错,api参考API目录 - self.nickname = Util.replaceT(user_profile_info.get("nickname")) - # 判断昵称映射表中是否已经存在该用户 - if self.nick_mapper.get_nickname(self.sec_user_id) is None: - # 如果不存在,添加昵称映射 - self.nick_mapper.add_mapping(self.sec_user_id, self.nickname) - # 根据映射表中的唯一标识获取用户的昵称,即使用户修改昵称也不会影响文件目录 - self.nickname = self.nick_mapper.get_nickname(self.sec_user_id) - Util.progress.console.print(f'[ 用户 ]:用户的昵称:{self.nickname},用户唯一标识:{self.sec_user_id}') - Util.log.info(f'[ 用户 ]:用户的昵称:{self.nickname},用户唯一标识:{self.sec_user_id}') - - # 用户初始接口URL生成 - self.profile_URL = await self.get_diff_type_url(self.config, self.sec_user_id, count, 0) - - # 创建用户文件夹 - self.path = self.create_user_folder(self.config, self.nickname) - - # 保存用户主页链接 - with open(Util.os.path.join(self.path, - self.nickname + '.txt'), - 'w') as f: - f.write(f"https://www.douyin.com/user/{self.sec_user_id}") - - Util.progress.console.print('[ 提示 ]:批量获取所有视频中!\r') - Util.log.info('[ 提示 ]:批量获取所有视频中!') - - aweme_data = await self.get_user_post_info(self.headers, self.profile_URL) - self.has_more = aweme_data[0].get("has_more") - self.max_cursor = aweme_data[0].get("max_cursor") - Util.progress.console.print(f'[ 提示 ]:抓获首页数据成功! 该页共{len(aweme_data)}个作品。\r') - Util.log.info(f'[ 提示 ]:抓获首页数据成功! 该页共{len(aweme_data)}个作品。') - - while True: - if Util.done_event.is_set(): - Util.progress.console.print("[ 提示 ]: 中断本次下载") - return - - # 首先处理当前的 aweme_data - await self.process_aweme_data(aweme_data) - - # 检查是否有更多作品需要请求 - if self.has_more == 0: - break - - # 如果有更多作品,则更新URL并请求新的数据 - self.profile_URL = await self.get_diff_type_url(self.config, - self.sec_user_id, - count, - self.max_cursor) - aweme_data = await self.get_user_post_info(self.headers, self.profile_URL) - self.has_more = aweme_data[0].get("has_more") - self.max_cursor = aweme_data[0].get("max_cursor") - except Exception as e: - Util.progress.console.print(f'[ 提示 ]:异常,{e}') - Util.log.error(f'[ 提示 ]:异常,{e},{Util.traceback.format_exc()}') - input('[ 提示 ]:按任意键退出程序!\r') - exit(0) diff --git a/Util/Resource.py b/Util/Resource.py deleted file mode 100644 index fcec3d73..00000000 --- a/Util/Resource.py +++ /dev/null @@ -1,648 +0,0 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2) -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore - -qt_resource_data = b"\ -\x00\x00\x22\x3e\ -\x3c\ -\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ -\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\ -\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\ -\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\ -\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x35\x2e\x30\x2e\x30\x2c\ -\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\ -\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\ -\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\ -\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ -\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ -\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ -\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ -\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ -\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ -\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\ -\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ -\x3d\x22\xe5\x9b\xbe\xe5\xb1\x82\x5f\x31\x22\x20\x78\x6d\x6c\x6e\ -\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\ -\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\ -\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\ -\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\ -\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\ -\x22\x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\ -\x74\x68\x3d\x22\x31\x35\x38\x70\x78\x22\x20\x68\x65\x69\x67\x68\ -\x74\x3d\x22\x33\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\ -\x3d\x22\x30\x20\x30\x20\x31\x35\x38\x20\x33\x34\x22\x20\x65\x6e\ -\x61\x62\x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\ -\x22\x6e\x65\x77\x20\x30\x20\x30\x20\x31\x35\x38\x20\x33\x34\x22\ -\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\ -\x65\x72\x76\x65\x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x66\x69\ -\x6c\x6c\x3d\x22\x23\x30\x30\x46\x41\x46\x30\x22\x20\x64\x3d\x22\ -\x4d\x31\x31\x2e\x36\x34\x2c\x31\x33\x2e\x34\x33\x34\x76\x2d\x31\ -\x2e\x33\x31\x31\x63\x2d\x30\x2e\x34\x35\x36\x2d\x30\x2e\x30\x35\ -\x35\x2d\x30\x2e\x39\x31\x31\x2d\x30\x2e\x30\x39\x31\x2d\x31\x2e\ -\x33\x38\x35\x2d\x30\x2e\x30\x39\x31\x63\x2d\x35\x2e\x36\x34\x37\ -\x2c\x30\x2d\x31\x30\x2e\x32\x35\x36\x2c\x34\x2e\x35\x38\x38\x2d\ -\x31\x30\x2e\x32\x35\x36\x2c\x31\x30\x2e\x32\x35\x31\x0d\x0a\x09\ -\x63\x30\x2c\x33\x2e\x34\x35\x39\x2c\x31\x2e\x37\x33\x31\x2c\x36\ -\x2e\x35\x33\x35\x2c\x34\x2e\x33\x37\x32\x2c\x38\x2e\x33\x39\x33\ -\x63\x2d\x31\x2e\x37\x31\x32\x2d\x31\x2e\x38\x33\x39\x2d\x32\x2e\ -\x37\x35\x2d\x34\x2e\x32\x37\x39\x2d\x32\x2e\x37\x35\x2d\x36\x2e\ -\x39\x37\x33\x43\x31\x2e\x36\x33\x39\x2c\x31\x38\x2e\x31\x31\x33\ -\x2c\x36\x2e\x31\x30\x32\x2c\x31\x33\x2e\x35\x36\x32\x2c\x31\x31\ -\x2e\x36\x34\x2c\x31\x33\x2e\x34\x33\x34\x7a\x22\x2f\x3e\x0d\x0a\ -\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x30\x30\x46\ -\x41\x46\x30\x22\x20\x64\x3d\x22\x4d\x31\x31\x2e\x38\x37\x36\x2c\ -\x32\x38\x2e\x33\x34\x36\x63\x32\x2e\x35\x31\x34\x2c\x30\x2c\x34\ -\x2e\x35\x37\x32\x2d\x32\x2e\x30\x30\x34\x2c\x34\x2e\x36\x36\x34\ -\x2d\x34\x2e\x34\x39\x38\x56\x31\x2e\x35\x34\x35\x68\x34\x2e\x30\ -\x38\x31\x63\x2d\x30\x2e\x30\x39\x31\x2d\x30\x2e\x34\x35\x35\x2d\ -\x30\x2e\x31\x32\x37\x2d\x30\x2e\x39\x32\x38\x2d\x30\x2e\x31\x32\ -\x37\x2d\x31\x2e\x34\x32\x48\x31\x34\x2e\x39\x32\x0d\x0a\x09\x76\ -\x32\x32\x2e\x33\x30\x33\x63\x2d\x30\x2e\x30\x39\x31\x2c\x32\x2e\ -\x34\x39\x34\x2d\x32\x2e\x31\x35\x2c\x34\x2e\x34\x39\x38\x2d\x34\ -\x2e\x36\x36\x34\x2c\x34\x2e\x34\x39\x38\x63\x2d\x30\x2e\x37\x38\ -\x33\x2c\x30\x2d\x31\x2e\x35\x33\x2d\x30\x2e\x32\x30\x31\x2d\x32\ -\x2e\x31\x36\x37\x2d\x30\x2e\x35\x34\x37\x43\x38\x2e\x39\x34\x34\ -\x2c\x32\x37\x2e\x35\x38\x2c\x31\x30\x2e\x33\x32\x38\x2c\x32\x38\ -\x2e\x33\x34\x36\x2c\x31\x31\x2e\x38\x37\x36\x2c\x32\x38\x2e\x33\ -\x34\x36\x7a\x22\x2f\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x66\x69\ -\x6c\x6c\x3d\x22\x23\x30\x30\x46\x41\x46\x30\x22\x20\x64\x3d\x22\ -\x4d\x32\x38\x2e\x32\x35\x31\x2c\x39\x2e\x31\x31\x39\x56\x37\x2e\ -\x38\x38\x31\x63\x2d\x31\x2e\x35\x34\x38\x2c\x30\x2d\x33\x2e\x30\ -\x30\x36\x2d\x30\x2e\x34\x35\x35\x2d\x34\x2e\x32\x32\x36\x2d\x31\ -\x2e\x32\x35\x36\x43\x32\x35\x2e\x31\x31\x38\x2c\x37\x2e\x38\x36\ -\x33\x2c\x32\x36\x2e\x35\x39\x34\x2c\x38\x2e\x37\x35\x35\x2c\x32\ -\x38\x2e\x32\x35\x31\x2c\x39\x2e\x31\x31\x39\x7a\x22\x2f\x3e\x0d\ -\x0a\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\ -\x30\x30\x35\x30\x22\x20\x64\x3d\x22\x4d\x32\x34\x2e\x30\x34\x35\ -\x2c\x36\x2e\x36\x32\x35\x63\x2d\x31\x2e\x31\x38\x34\x2d\x31\x2e\ -\x33\x36\x35\x2d\x31\x2e\x39\x31\x33\x2d\x33\x2e\x31\x33\x31\x2d\ -\x31\x2e\x39\x31\x33\x2d\x35\x2e\x30\x38\x68\x2d\x31\x2e\x34\x39\ -\x34\x43\x32\x31\x2e\x30\x33\x39\x2c\x33\x2e\x36\x35\x38\x2c\x32\ -\x32\x2e\x32\x39\x36\x2c\x35\x2e\x34\x37\x39\x2c\x32\x34\x2e\x30\ -\x34\x35\x2c\x36\x2e\x36\x32\x35\x7a\x22\x2f\x3e\x0d\x0a\x3c\x70\ -\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x30\x30\x35\ -\x30\x22\x20\x64\x3d\x22\x4d\x31\x30\x2e\x32\x35\x35\x2c\x31\x37\ -\x2e\x35\x38\x34\x63\x2d\x32\x2e\x35\x38\x36\x2c\x30\x2d\x34\x2e\ -\x36\x38\x32\x2c\x32\x2e\x30\x39\x34\x2d\x34\x2e\x36\x38\x32\x2c\ -\x34\x2e\x36\x38\x63\x30\x2c\x31\x2e\x38\x30\x33\x2c\x31\x2e\x30\ -\x32\x31\x2c\x33\x2e\x33\x35\x2c\x32\x2e\x35\x31\x34\x2c\x34\x2e\ -\x31\x33\x33\x0d\x0a\x09\x63\x2d\x30\x2e\x35\x34\x36\x2d\x30\x2e\ -\x37\x36\x35\x2d\x30\x2e\x38\x39\x33\x2d\x31\x2e\x37\x31\x31\x2d\ -\x30\x2e\x38\x39\x33\x2d\x32\x2e\x37\x32\x39\x63\x30\x2d\x32\x2e\ -\x35\x38\x36\x2c\x32\x2e\x30\x39\x35\x2d\x34\x2e\x36\x38\x31\x2c\ -\x34\x2e\x36\x38\x32\x2d\x34\x2e\x36\x38\x31\x63\x30\x2e\x34\x37\ -\x34\x2c\x30\x2c\x30\x2e\x39\x34\x37\x2c\x30\x2e\x30\x37\x34\x2c\ -\x31\x2e\x33\x38\x34\x2c\x30\x2e\x32\x32\x76\x2d\x35\x2e\x36\x38\ -\x0d\x0a\x09\x63\x2d\x30\x2e\x34\x35\x35\x2d\x30\x2e\x30\x35\x35\ -\x2d\x30\x2e\x39\x31\x31\x2d\x30\x2e\x30\x39\x31\x2d\x31\x2e\x33\ -\x38\x34\x2d\x30\x2e\x30\x39\x31\x63\x2d\x30\x2e\x30\x37\x33\x2c\ -\x30\x2d\x30\x2e\x31\x36\x34\x2c\x30\x2d\x30\x2e\x32\x33\x37\x2c\ -\x30\x76\x34\x2e\x33\x37\x43\x31\x31\x2e\x32\x30\x32\x2c\x31\x37\ -\x2e\x36\x35\x38\x2c\x31\x30\x2e\x37\x34\x37\x2c\x31\x37\x2e\x35\ -\x38\x34\x2c\x31\x30\x2e\x32\x35\x35\x2c\x31\x37\x2e\x35\x38\x34\ -\x7a\x22\x2f\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\ -\x3d\x22\x23\x46\x46\x30\x30\x35\x30\x22\x20\x64\x3d\x22\x4d\x32\ -\x38\x2e\x32\x35\x32\x2c\x39\x2e\x31\x31\x39\x76\x34\x2e\x33\x33\ -\x33\x63\x2d\x32\x2e\x38\x39\x36\x2c\x30\x2d\x35\x2e\x35\x35\x36\ -\x2d\x30\x2e\x39\x32\x38\x2d\x37\x2e\x37\x34\x32\x2d\x32\x2e\x34\ -\x39\x34\x76\x31\x31\x2e\x33\x30\x36\x63\x30\x2c\x35\x2e\x36\x34\ -\x36\x2d\x34\x2e\x35\x39\x2c\x31\x30\x2e\x32\x35\x2d\x31\x30\x2e\ -\x32\x35\x35\x2c\x31\x30\x2e\x32\x35\x0d\x0a\x09\x63\x2d\x32\x2e\ -\x31\x38\x36\x2c\x30\x2d\x34\x2e\x32\x30\x38\x2d\x30\x2e\x36\x39\ -\x31\x2d\x35\x2e\x38\x36\x36\x2d\x31\x2e\x38\x35\x35\x63\x31\x2e\ -\x38\x37\x36\x2c\x32\x2e\x30\x30\x33\x2c\x34\x2e\x35\x33\x36\x2c\ -\x33\x2e\x32\x37\x37\x2c\x37\x2e\x35\x30\x35\x2c\x33\x2e\x32\x37\ -\x37\x63\x35\x2e\x36\x34\x37\x2c\x30\x2c\x31\x30\x2e\x32\x35\x36\ -\x2d\x34\x2e\x35\x38\x38\x2c\x31\x30\x2e\x32\x35\x36\x2d\x31\x30\ -\x2e\x32\x35\x32\x56\x31\x32\x2e\x33\x37\x38\x0d\x0a\x09\x63\x32\ -\x2e\x31\x38\x36\x2c\x31\x2e\x35\x36\x36\x2c\x34\x2e\x38\x36\x34\ -\x2c\x32\x2e\x34\x39\x35\x2c\x37\x2e\x37\x34\x32\x2c\x32\x2e\x34\ -\x39\x35\x56\x39\x2e\x33\x30\x31\x43\x32\x39\x2e\x33\x32\x37\x2c\ -\x39\x2e\x32\x38\x33\x2c\x32\x38\x2e\x37\x38\x31\x2c\x39\x2e\x32\ -\x32\x39\x2c\x32\x38\x2e\x32\x35\x32\x2c\x39\x2e\x31\x31\x39\x7a\ -\x22\x2f\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\ -\x22\x23\x46\x46\x46\x46\x46\x46\x22\x20\x64\x3d\x22\x4d\x32\x30\ -\x2e\x35\x30\x39\x2c\x32\x32\x2e\x32\x36\x36\x56\x31\x30\x2e\x39\ -\x35\x38\x63\x32\x2e\x31\x38\x36\x2c\x31\x2e\x35\x36\x36\x2c\x34\ -\x2e\x38\x36\x34\x2c\x32\x2e\x34\x39\x34\x2c\x37\x2e\x37\x34\x32\ -\x2c\x32\x2e\x34\x39\x34\x56\x39\x2e\x31\x32\x63\x2d\x31\x2e\x36\ -\x37\x36\x2d\x30\x2e\x33\x36\x34\x2d\x33\x2e\x31\x33\x33\x2d\x31\ -\x2e\x32\x35\x36\x2d\x34\x2e\x32\x32\x37\x2d\x32\x2e\x34\x39\x34\ -\x0d\x0a\x09\x63\x2d\x31\x2e\x37\x34\x39\x2d\x31\x2e\x31\x32\x39\ -\x2d\x33\x2e\x30\x30\x35\x2d\x32\x2e\x39\x34\x39\x2d\x33\x2e\x33\ -\x38\x38\x2d\x35\x2e\x30\x38\x68\x2d\x34\x2e\x30\x38\x56\x32\x33\ -\x2e\x38\x35\x63\x2d\x30\x2e\x30\x39\x31\x2c\x32\x2e\x34\x39\x34\ -\x2d\x32\x2e\x31\x35\x2c\x34\x2e\x34\x39\x36\x2d\x34\x2e\x36\x36\ -\x34\x2c\x34\x2e\x34\x39\x36\x63\x2d\x31\x2e\x35\x36\x36\x2c\x30\ -\x2d\x32\x2e\x39\x35\x31\x2d\x30\x2e\x37\x36\x35\x2d\x33\x2e\x37\ -\x38\x39\x2d\x31\x2e\x39\x34\x37\x0d\x0a\x09\x63\x2d\x31\x2e\x34\ -\x39\x34\x2d\x30\x2e\x37\x38\x33\x2d\x32\x2e\x35\x31\x34\x2d\x32\ -\x2e\x33\x35\x2d\x32\x2e\x35\x31\x34\x2d\x34\x2e\x31\x33\x33\x63\ -\x30\x2d\x32\x2e\x35\x38\x36\x2c\x32\x2e\x30\x39\x35\x2d\x34\x2e\ -\x36\x38\x2c\x34\x2e\x36\x38\x32\x2d\x34\x2e\x36\x38\x63\x30\x2e\ -\x34\x37\x34\x2c\x30\x2c\x30\x2e\x39\x34\x37\x2c\x30\x2e\x30\x37\ -\x32\x2c\x31\x2e\x33\x38\x34\x2c\x30\x2e\x32\x31\x39\x76\x2d\x34\ -\x2e\x33\x37\x0d\x0a\x09\x63\x2d\x35\x2e\x35\x33\x36\x2c\x30\x2e\ -\x31\x32\x37\x2d\x31\x30\x2c\x34\x2e\x36\x37\x38\x2d\x31\x30\x2c\ -\x31\x30\x2e\x32\x33\x31\x63\x30\x2c\x32\x2e\x36\x39\x35\x2c\x31\ -\x2e\x30\x33\x39\x2c\x35\x2e\x31\x35\x32\x2c\x32\x2e\x37\x35\x31\ -\x2c\x36\x2e\x39\x37\x35\x63\x31\x2e\x36\x35\x38\x2c\x31\x2e\x31\ -\x36\x34\x2c\x33\x2e\x36\x39\x38\x2c\x31\x2e\x38\x35\x36\x2c\x35\ -\x2e\x38\x36\x36\x2c\x31\x2e\x38\x35\x36\x0d\x0a\x09\x43\x31\x35\ -\x2e\x39\x2c\x33\x32\x2e\x35\x31\x36\x2c\x32\x30\x2e\x35\x30\x39\ -\x2c\x32\x37\x2e\x39\x30\x38\x2c\x32\x30\x2e\x35\x30\x39\x2c\x32\ -\x32\x2e\x32\x36\x36\x7a\x22\x2f\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\ -\x09\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\ -\x46\x46\x46\x46\x22\x20\x64\x3d\x22\x4d\x34\x34\x2e\x35\x37\x36\ -\x2c\x31\x35\x2e\x38\x37\x36\x63\x30\x2e\x33\x35\x33\x2d\x30\x2e\ -\x30\x38\x2c\x30\x2e\x35\x32\x32\x2c\x30\x2e\x30\x36\x34\x2c\x30\ -\x2e\x35\x30\x36\x2c\x30\x2e\x34\x33\x34\x6c\x2d\x30\x2e\x31\x32\ -\x2c\x32\x2e\x32\x36\x35\x63\x2d\x30\x2e\x30\x31\x37\x2c\x30\x2e\ -\x33\x37\x2d\x30\x2e\x32\x30\x31\x2c\x30\x2e\x35\x39\x35\x2d\x30\ -\x2e\x35\x35\x34\x2c\x30\x2e\x36\x37\x35\x0d\x0a\x09\x09\x6c\x2d\ -\x31\x2e\x36\x31\x34\x2c\x30\x2e\x34\x30\x39\x76\x35\x2e\x32\x30\ -\x34\x63\x30\x2c\x30\x2e\x38\x32\x2d\x30\x2e\x30\x38\x39\x2c\x31\ -\x2e\x34\x35\x35\x2d\x30\x2e\x32\x36\x35\x2c\x31\x2e\x39\x30\x34\ -\x63\x2d\x30\x2e\x31\x36\x31\x2c\x30\x2e\x34\x34\x39\x2d\x30\x2e\ -\x34\x36\x36\x2c\x30\x2e\x37\x39\x35\x2d\x30\x2e\x39\x31\x36\x2c\ -\x31\x2e\x30\x33\x35\x63\x2d\x30\x2e\x37\x32\x33\x2c\x30\x2e\x34\ -\x31\x38\x2d\x31\x2e\x37\x37\x35\x2c\x30\x2e\x36\x31\x39\x2d\x33\ -\x2e\x31\x35\x37\x2c\x30\x2e\x36\x30\x34\x0d\x0a\x09\x09\x63\x2d\ -\x30\x2e\x34\x39\x38\x2c\x30\x2e\x30\x31\x36\x2d\x30\x2e\x37\x36\ -\x33\x2d\x30\x2e\x31\x36\x32\x2d\x30\x2e\x37\x39\x35\x2d\x30\x2e\ -\x35\x33\x31\x6c\x2d\x30\x2e\x32\x34\x31\x2d\x32\x2e\x30\x34\x37\ -\x63\x2d\x30\x2e\x30\x33\x32\x2d\x30\x2e\x33\x37\x2c\x30\x2e\x31\ -\x33\x37\x2d\x30\x2e\x35\x35\x35\x2c\x30\x2e\x35\x30\x36\x2d\x30\ -\x2e\x35\x35\x35\x68\x30\x2e\x39\x38\x38\x0d\x0a\x09\x09\x63\x30\ -\x2e\x33\x30\x35\x2c\x30\x2e\x30\x31\x36\x2c\x30\x2e\x34\x35\x38\ -\x2d\x30\x2e\x31\x32\x39\x2c\x30\x2e\x34\x35\x38\x2d\x30\x2e\x34\ -\x33\x34\x76\x2d\x34\x2e\x33\x33\x38\x6c\x2d\x31\x2e\x36\x36\x32\ -\x2c\x30\x2e\x34\x31\x63\x2d\x30\x2e\x33\x35\x34\x2c\x30\x2e\x30\ -\x38\x2d\x30\x2e\x35\x33\x39\x2d\x30\x2e\x30\x36\x34\x2d\x30\x2e\ -\x35\x35\x34\x2d\x30\x2e\x34\x33\x34\x6c\x2d\x30\x2e\x32\x34\x31\ -\x2d\x32\x2e\x33\x33\x37\x0d\x0a\x09\x09\x63\x2d\x30\x2e\x30\x31\ -\x36\x2d\x30\x2e\x33\x37\x2c\x30\x2e\x31\x36\x31\x2d\x30\x2e\x35\ -\x39\x35\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x36\x37\x35\x6c\x31\x2e\ -\x39\x32\x37\x2d\x30\x2e\x33\x38\x36\x76\x2d\x33\x2e\x36\x36\x32\ -\x68\x2d\x32\x2e\x31\x36\x38\x63\x2d\x30\x2e\x33\x37\x2c\x30\x2d\ -\x30\x2e\x35\x32\x32\x2d\x30\x2e\x31\x37\x37\x2d\x30\x2e\x34\x35\ -\x38\x2d\x30\x2e\x35\x33\x6c\x30\x2e\x34\x31\x2d\x32\x2e\x31\x36\ -\x38\x0d\x0a\x09\x09\x63\x30\x2e\x30\x36\x34\x2d\x30\x2e\x33\x35\ -\x33\x2c\x30\x2e\x32\x38\x31\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x36\ -\x35\x2d\x30\x2e\x35\x33\x68\x31\x2e\x35\x36\x36\x56\x36\x2e\x32\ -\x38\x37\x63\x30\x2d\x30\x2e\x33\x37\x2c\x30\x2e\x31\x37\x37\x2d\ -\x30\x2e\x35\x33\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x34\x38\x32\x6c\ -\x32\x2e\x33\x36\x31\x2c\x30\x2e\x34\x35\x38\x63\x30\x2e\x33\x35\ -\x33\x2c\x30\x2e\x30\x34\x38\x2c\x30\x2e\x35\x33\x2c\x30\x2e\x32\ -\x35\x37\x2c\x30\x2e\x35\x33\x2c\x30\x2e\x36\x32\x36\x76\x33\x2e\ -\x33\x30\x31\x0d\x0a\x09\x09\x68\x32\x2e\x30\x39\x36\x63\x30\x2e\ -\x33\x36\x39\x2c\x30\x2c\x30\x2e\x35\x32\x31\x2c\x30\x2e\x31\x37\ -\x37\x2c\x30\x2e\x34\x35\x38\x2c\x30\x2e\x35\x33\x6c\x2d\x30\x2e\ -\x34\x31\x2c\x32\x2e\x31\x36\x38\x63\x2d\x30\x2e\x30\x36\x34\x2c\ -\x30\x2e\x33\x35\x34\x2d\x30\x2e\x32\x38\x31\x2c\x30\x2e\x35\x33\ -\x2d\x30\x2e\x36\x35\x2c\x30\x2e\x35\x33\x68\x2d\x31\x2e\x34\x39\ -\x34\x76\x32\x2e\x38\x39\x31\x4c\x34\x34\x2e\x35\x37\x36\x2c\x31\ -\x35\x2e\x38\x37\x36\x7a\x20\x4d\x35\x31\x2e\x34\x39\x31\x2c\x31\ -\x35\x2e\x32\x35\x0d\x0a\x09\x09\x63\x30\x2e\x32\x37\x33\x2c\x30\ -\x2e\x32\x32\x35\x2c\x30\x2e\x32\x38\x39\x2c\x30\x2e\x34\x36\x36\ -\x2c\x30\x2e\x30\x34\x38\x2c\x30\x2e\x37\x32\x33\x6c\x2d\x31\x2e\ -\x35\x36\x35\x2c\x31\x2e\x36\x38\x38\x63\x2d\x30\x2e\x32\x34\x31\ -\x2c\x30\x2e\x32\x35\x36\x2d\x30\x2e\x34\x37\x35\x2c\x30\x2e\x32\ -\x38\x31\x2d\x30\x2e\x36\x39\x39\x2c\x30\x2e\x30\x37\x32\x63\x2d\ -\x30\x2e\x33\x35\x34\x2d\x30\x2e\x34\x31\x38\x2d\x31\x2e\x33\x38\ -\x32\x2d\x31\x2e\x31\x31\x37\x2d\x33\x2e\x30\x38\x34\x2d\x32\x2e\ -\x30\x39\x37\x0d\x0a\x09\x09\x63\x2d\x30\x2e\x34\x31\x38\x2d\x30\ -\x2e\x32\x35\x37\x2d\x30\x2e\x34\x39\x2d\x30\x2e\x34\x39\x38\x2d\ -\x30\x2e\x32\x31\x37\x2d\x30\x2e\x37\x32\x33\x6c\x31\x2e\x36\x36\ -\x32\x2d\x31\x2e\x34\x34\x36\x63\x30\x2e\x32\x37\x33\x2d\x30\x2e\ -\x32\x32\x35\x2c\x30\x2e\x36\x31\x2d\x30\x2e\x32\x33\x32\x2c\x31\ -\x2e\x30\x31\x33\x2d\x30\x2e\x30\x32\x34\x43\x34\x39\x2e\x38\x38\ -\x35\x2c\x31\x34\x2e\x30\x35\x34\x2c\x35\x30\x2e\x38\x33\x32\x2c\ -\x31\x34\x2e\x36\x35\x36\x2c\x35\x31\x2e\x34\x39\x31\x2c\x31\x35\ -\x2e\x32\x35\x7a\x0d\x0a\x09\x09\x20\x4d\x35\x38\x2e\x39\x31\x32\ -\x2c\x31\x38\x2e\x39\x36\x31\x76\x32\x2e\x32\x31\x37\x63\x30\x2c\ -\x30\x2e\x33\x36\x39\x2d\x30\x2e\x31\x38\x35\x2c\x30\x2e\x35\x38\ -\x36\x2d\x30\x2e\x35\x35\x34\x2c\x30\x2e\x36\x35\x6c\x2d\x31\x2e\ -\x39\x32\x38\x2c\x30\x2e\x33\x33\x37\x76\x35\x2e\x38\x38\x63\x30\ -\x2c\x30\x2e\x33\x36\x39\x2d\x30\x2e\x31\x37\x37\x2c\x30\x2e\x35\ -\x32\x39\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x34\x38\x6c\x2d\x32\x2e\ -\x34\x30\x39\x2d\x30\x2e\x34\x35\x37\x0d\x0a\x09\x09\x63\x2d\x30\ -\x2e\x33\x35\x34\x2d\x30\x2e\x30\x34\x39\x2d\x30\x2e\x35\x33\x2d\ -\x30\x2e\x32\x35\x38\x2d\x30\x2e\x35\x33\x2d\x30\x2e\x36\x32\x37\ -\x76\x2d\x34\x2e\x36\x39\x37\x6c\x2d\x36\x2e\x35\x30\x36\x2c\x31\ -\x2e\x31\x33\x32\x63\x2d\x30\x2e\x33\x35\x34\x2c\x30\x2e\x30\x34\ -\x38\x2d\x30\x2e\x35\x33\x2d\x30\x2e\x31\x31\x32\x2d\x30\x2e\x35\ -\x33\x2d\x30\x2e\x34\x38\x31\x6c\x2d\x30\x2e\x30\x34\x38\x2d\x32\ -\x2e\x33\x31\x33\x0d\x0a\x09\x09\x63\x30\x2d\x30\x2e\x33\x37\x2c\ -\x30\x2e\x31\x38\x35\x2d\x30\x2e\x35\x37\x39\x2c\x30\x2e\x35\x35\ -\x34\x2d\x30\x2e\x36\x32\x36\x6c\x36\x2e\x35\x32\x39\x2d\x31\x2e\ -\x30\x38\x35\x56\x36\x2e\x32\x36\x33\x63\x30\x2d\x30\x2e\x33\x36\ -\x39\x2c\x30\x2e\x31\x37\x37\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x35\ -\x33\x2d\x30\x2e\x34\x38\x32\x4c\x35\x35\x2e\x39\x2c\x36\x2e\x32\ -\x33\x39\x63\x30\x2e\x33\x35\x34\x2c\x30\x2e\x30\x34\x38\x2c\x30\ -\x2e\x35\x33\x2c\x30\x2e\x32\x35\x37\x2c\x30\x2e\x35\x33\x2c\x30\ -\x2e\x36\x32\x36\x0d\x0a\x09\x09\x76\x31\x31\x2e\x39\x35\x31\x6c\ -\x31\x2e\x39\x35\x32\x2d\x30\x2e\x33\x33\x38\x43\x35\x38\x2e\x37\ -\x33\x35\x2c\x31\x38\x2e\x34\x33\x31\x2c\x35\x38\x2e\x39\x31\x32\ -\x2c\x31\x38\x2e\x35\x39\x32\x2c\x35\x38\x2e\x39\x31\x32\x2c\x31\ -\x38\x2e\x39\x36\x31\x7a\x20\x4d\x35\x32\x2e\x34\x35\x35\x2c\x39\ -\x2e\x33\x39\x35\x63\x30\x2e\x32\x35\x37\x2c\x30\x2e\x32\x34\x31\ -\x2c\x30\x2e\x32\x35\x37\x2c\x30\x2e\x34\x39\x2c\x30\x2c\x30\x2e\ -\x37\x34\x37\x6c\x2d\x31\x2e\x35\x36\x36\x2c\x31\x2e\x34\x39\x34\ -\x0d\x0a\x09\x09\x63\x2d\x30\x2e\x32\x35\x37\x2c\x30\x2e\x32\x35\ -\x37\x2d\x30\x2e\x35\x30\x36\x2c\x30\x2e\x32\x37\x33\x2d\x30\x2e\ -\x37\x34\x37\x2c\x30\x2e\x30\x34\x38\x63\x2d\x30\x2e\x33\x36\x39\ -\x2d\x30\x2e\x34\x39\x38\x2d\x31\x2e\x33\x31\x36\x2d\x31\x2e\x32\ -\x30\x35\x2d\x32\x2e\x38\x34\x33\x2d\x32\x2e\x31\x32\x63\x2d\x30\ -\x2e\x33\x38\x35\x2d\x30\x2e\x32\x35\x37\x2d\x30\x2e\x34\x33\x34\ -\x2d\x30\x2e\x34\x39\x38\x2d\x30\x2e\x31\x34\x35\x2d\x30\x2e\x37\ -\x32\x33\x6c\x31\x2e\x36\x36\x32\x2d\x31\x2e\x33\x30\x31\x0d\x0a\ -\x09\x09\x63\x30\x2e\x32\x39\x2d\x30\x2e\x32\x32\x35\x2c\x30\x2e\ -\x36\x32\x37\x2d\x30\x2e\x32\x32\x35\x2c\x31\x2e\x30\x31\x33\x2c\ -\x30\x43\x35\x31\x2e\x30\x31\x38\x2c\x38\x2e\x31\x36\x36\x2c\x35\ -\x31\x2e\x38\x39\x33\x2c\x38\x2e\x37\x38\x35\x2c\x35\x32\x2e\x34\ -\x35\x35\x2c\x39\x2e\x33\x39\x35\x7a\x22\x2f\x3e\x0d\x0a\x09\x3c\ -\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\x46\ -\x46\x46\x22\x20\x64\x3d\x22\x4d\x37\x38\x2e\x34\x37\x37\x2c\x31\ -\x33\x2e\x32\x35\x68\x34\x2e\x31\x36\x39\x63\x30\x2e\x33\x36\x39\ -\x2c\x30\x2c\x30\x2e\x35\x32\x31\x2c\x30\x2e\x31\x37\x37\x2c\x30\ -\x2e\x34\x35\x38\x2c\x30\x2e\x35\x33\x6c\x2d\x30\x2e\x34\x31\x2c\ -\x32\x2e\x31\x34\x35\x63\x2d\x30\x2e\x30\x36\x34\x2c\x30\x2e\x33\ -\x35\x34\x2d\x30\x2e\x32\x38\x31\x2c\x30\x2e\x35\x33\x2d\x30\x2e\ -\x36\x35\x2c\x30\x2e\x35\x33\x48\x36\x31\x2e\x32\x30\x31\x0d\x0a\ -\x09\x09\x63\x2d\x30\x2e\x33\x37\x2c\x30\x2d\x30\x2e\x35\x32\x32\ -\x2d\x30\x2e\x31\x37\x36\x2d\x30\x2e\x34\x35\x38\x2d\x30\x2e\x35\ -\x33\x6c\x30\x2e\x34\x31\x2d\x32\x2e\x31\x34\x35\x63\x30\x2e\x30\ -\x36\x33\x2d\x30\x2e\x33\x35\x33\x2c\x30\x2e\x32\x38\x2d\x30\x2e\ -\x35\x33\x2c\x30\x2e\x36\x35\x2d\x30\x2e\x35\x33\x68\x33\x2e\x38\ -\x37\x39\x6c\x2d\x30\x2e\x32\x31\x37\x2d\x30\x2e\x35\x35\x34\x0d\ -\x0a\x09\x09\x63\x2d\x30\x2e\x33\x36\x39\x2d\x30\x2e\x39\x37\x39\ -\x2d\x30\x2e\x30\x34\x2d\x31\x2e\x35\x35\x38\x2c\x30\x2e\x39\x38\ -\x38\x2d\x31\x2e\x37\x33\x34\x6c\x30\x2e\x37\x32\x33\x2d\x30\x2e\ -\x31\x34\x35\x68\x2d\x34\x2e\x36\x30\x33\x63\x2d\x30\x2e\x33\x36\ -\x39\x2c\x30\x2d\x30\x2e\x35\x32\x31\x2d\x30\x2e\x31\x37\x36\x2d\ -\x30\x2e\x34\x35\x37\x2d\x30\x2e\x35\x33\x6c\x30\x2e\x34\x30\x39\ -\x2d\x32\x2e\x31\x34\x35\x0d\x0a\x09\x09\x63\x30\x2e\x30\x36\x34\ -\x2d\x30\x2e\x33\x35\x33\x2c\x30\x2e\x32\x38\x31\x2d\x30\x2e\x35\ -\x33\x2c\x30\x2e\x36\x35\x2d\x30\x2e\x35\x33\x68\x36\x2e\x39\x33\ -\x39\x6c\x2d\x30\x2e\x34\x38\x32\x2d\x31\x2e\x31\x33\x33\x63\x2d\ -\x30\x2e\x31\x32\x38\x2d\x30\x2e\x33\x33\x37\x2d\x30\x2e\x30\x30\ -\x38\x2d\x30\x2e\x35\x30\x36\x2c\x30\x2e\x33\x36\x32\x2d\x30\x2e\ -\x35\x30\x36\x6c\x32\x2e\x39\x36\x33\x2d\x30\x2e\x30\x32\x34\x0d\ -\x0a\x09\x09\x63\x30\x2e\x33\x36\x39\x2c\x30\x2c\x30\x2e\x36\x32\ -\x37\x2c\x30\x2e\x31\x37\x37\x2c\x30\x2e\x37\x37\x31\x2c\x30\x2e\ -\x35\x33\x6c\x30\x2e\x35\x30\x36\x2c\x31\x2e\x31\x33\x33\x68\x37\ -\x2e\x33\x32\x34\x63\x30\x2e\x33\x37\x2c\x30\x2c\x30\x2e\x35\x32\ -\x32\x2c\x30\x2e\x31\x37\x37\x2c\x30\x2e\x34\x35\x38\x2c\x30\x2e\ -\x35\x33\x6c\x2d\x30\x2e\x34\x30\x39\x2c\x32\x2e\x31\x34\x35\x63\ -\x2d\x30\x2e\x30\x36\x34\x2c\x30\x2e\x33\x35\x34\x2d\x30\x2e\x32\ -\x38\x31\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x36\x35\x2c\x30\x2e\x35\ -\x33\x0d\x0a\x09\x09\x48\x37\x37\x2e\x32\x6c\x30\x2e\x36\x37\x35\ -\x2c\x30\x2e\x31\x34\x35\x63\x31\x2e\x30\x32\x37\x2c\x30\x2e\x31\ -\x39\x32\x2c\x31\x2e\x33\x32\x35\x2c\x30\x2e\x37\x36\x33\x2c\x30\ -\x2e\x38\x39\x31\x2c\x31\x2e\x37\x31\x4c\x37\x38\x2e\x34\x37\x37\ -\x2c\x31\x33\x2e\x32\x35\x7a\x20\x4d\x36\x34\x2e\x38\x36\x33\x2c\ -\x32\x38\x2e\x36\x32\x33\x63\x2d\x30\x2e\x38\x38\x34\x2c\x30\x2d\ -\x31\x2e\x33\x32\x35\x2d\x30\x2e\x34\x34\x32\x2d\x31\x2e\x33\x32\ -\x35\x2d\x31\x2e\x33\x32\x36\x76\x2d\x38\x2e\x33\x33\x36\x0d\x0a\ -\x09\x09\x63\x30\x2d\x30\x2e\x38\x38\x34\x2c\x30\x2e\x34\x34\x31\ -\x2d\x31\x2e\x33\x32\x36\x2c\x31\x2e\x33\x32\x35\x2d\x31\x2e\x33\ -\x32\x36\x68\x31\x34\x2e\x34\x30\x39\x63\x30\x2e\x38\x38\x33\x2c\ -\x30\x2c\x31\x2e\x33\x32\x35\x2c\x30\x2e\x34\x34\x32\x2c\x31\x2e\ -\x33\x32\x35\x2c\x31\x2e\x33\x32\x36\x76\x38\x2e\x33\x33\x36\x63\ -\x30\x2c\x30\x2e\x38\x38\x34\x2d\x30\x2e\x34\x34\x32\x2c\x31\x2e\ -\x33\x32\x36\x2d\x31\x2e\x33\x32\x35\x2c\x31\x2e\x33\x32\x36\x48\ -\x36\x34\x2e\x38\x36\x33\x7a\x0d\x0a\x09\x09\x20\x4d\x37\x36\x2e\ -\x37\x36\x37\x2c\x32\x31\x2e\x32\x39\x38\x63\x30\x2d\x30\x2e\x34\ -\x36\x36\x2d\x30\x2e\x32\x33\x33\x2d\x30\x2e\x36\x39\x38\x2d\x30\ -\x2e\x36\x39\x39\x2d\x30\x2e\x36\x39\x38\x68\x2d\x38\x2e\x31\x39\ -\x32\x63\x2d\x30\x2e\x34\x36\x36\x2c\x30\x2d\x30\x2e\x36\x39\x38\ -\x2c\x30\x2e\x32\x33\x32\x2d\x30\x2e\x36\x39\x38\x2c\x30\x2e\x36\ -\x39\x38\x76\x30\x2e\x34\x35\x38\x68\x39\x2e\x35\x39\x56\x32\x31\ -\x2e\x32\x39\x38\x7a\x20\x4d\x36\x37\x2e\x31\x37\x37\x2c\x32\x34\ -\x2e\x39\x38\x34\x0d\x0a\x09\x09\x63\x30\x2c\x30\x2e\x34\x36\x36\ -\x2c\x30\x2e\x32\x33\x32\x2c\x30\x2e\x36\x39\x39\x2c\x30\x2e\x36\ -\x39\x38\x2c\x30\x2e\x36\x39\x39\x68\x38\x2e\x31\x39\x32\x63\x30\ -\x2e\x34\x36\x36\x2c\x30\x2c\x30\x2e\x36\x39\x39\x2d\x30\x2e\x32\ -\x33\x33\x2c\x30\x2e\x36\x39\x39\x2d\x30\x2e\x36\x39\x39\x56\x32\ -\x34\x2e\x34\x33\x68\x2d\x39\x2e\x35\x39\x56\x32\x34\x2e\x39\x38\ -\x34\x7a\x20\x4d\x37\x35\x2e\x32\x34\x38\x2c\x31\x30\x2e\x38\x31\ -\x36\x68\x2d\x36\x2e\x35\x37\x37\x6c\x30\x2e\x39\x38\x37\x2c\x32\ -\x2e\x34\x33\x34\x0d\x0a\x09\x09\x68\x34\x2e\x38\x36\x37\x4c\x37\ -\x35\x2e\x32\x34\x38\x2c\x31\x30\x2e\x38\x31\x36\x7a\x22\x2f\x3e\ -\x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\ -\x46\x46\x46\x46\x46\x46\x22\x20\x64\x3d\x22\x4d\x31\x30\x37\x2e\ -\x31\x30\x32\x2c\x31\x35\x2e\x36\x33\x36\x6c\x2d\x30\x2e\x34\x35\ -\x38\x2c\x32\x2e\x34\x38\x31\x63\x2d\x30\x2e\x30\x34\x38\x2c\x30\ -\x2e\x33\x35\x34\x2d\x30\x2e\x32\x35\x37\x2c\x30\x2e\x35\x33\x2d\ -\x30\x2e\x36\x32\x36\x2c\x30\x2e\x35\x33\x48\x39\x35\x2e\x37\x35\ -\x33\x0d\x0a\x09\x09\x63\x2d\x31\x2e\x31\x35\x36\x2c\x31\x2e\x38\ -\x2d\x32\x2e\x33\x35\x34\x2c\x33\x2e\x33\x37\x33\x2d\x33\x2e\x35\ -\x39\x2c\x34\x2e\x37\x32\x33\x63\x2d\x30\x2e\x33\x30\x36\x2c\x30\ -\x2e\x33\x35\x33\x2d\x30\x2e\x32\x33\x33\x2c\x30\x2e\x35\x32\x32\ -\x2c\x30\x2e\x32\x31\x37\x2c\x30\x2e\x35\x30\x36\x6c\x37\x2e\x38\ -\x35\x34\x2d\x30\x2e\x34\x33\x34\x6c\x2d\x31\x2e\x38\x37\x39\x2d\ -\x32\x2e\x36\x32\x36\x0d\x0a\x09\x09\x63\x2d\x30\x2e\x32\x30\x39\ -\x2d\x30\x2e\x32\x38\x39\x2d\x30\x2e\x31\x33\x37\x2d\x30\x2e\x34\ -\x37\x35\x2c\x30\x2e\x32\x31\x37\x2d\x30\x2e\x35\x35\x35\x6c\x32\ -\x2e\x35\x35\x34\x2d\x30\x2e\x37\x32\x33\x63\x30\x2e\x33\x35\x34\ -\x2d\x30\x2e\x30\x38\x2c\x30\x2e\x36\x34\x33\x2c\x30\x2e\x30\x31\ -\x36\x2c\x30\x2e\x38\x36\x37\x2c\x30\x2e\x32\x38\x39\x63\x30\x2e\ -\x35\x36\x33\x2c\x30\x2e\x36\x34\x33\x2c\x31\x2e\x33\x33\x33\x2c\ -\x31\x2e\x36\x39\x35\x2c\x32\x2e\x33\x31\x33\x2c\x33\x2e\x31\x35\ -\x36\x0d\x0a\x09\x09\x63\x30\x2e\x39\x37\x39\x2c\x31\x2e\x34\x34\ -\x35\x2c\x31\x2e\x36\x36\x32\x2c\x32\x2e\x35\x38\x36\x2c\x32\x2e\ -\x30\x34\x38\x2c\x33\x2e\x34\x32\x32\x63\x30\x2e\x31\x34\x35\x2c\ -\x30\x2e\x32\x38\x39\x2c\x30\x2e\x30\x34\x38\x2c\x30\x2e\x34\x38\ -\x39\x2d\x30\x2e\x32\x38\x39\x2c\x30\x2e\x36\x30\x32\x6c\x2d\x32\ -\x2e\x36\x39\x38\x2c\x30\x2e\x38\x39\x32\x63\x2d\x30\x2e\x33\x33\ -\x38\x2c\x30\x2e\x31\x31\x32\x2d\x30\x2e\x35\x39\x35\x2c\x30\x2d\ -\x30\x2e\x37\x37\x31\x2d\x30\x2e\x33\x33\x37\x0d\x0a\x09\x09\x6c\ -\x2d\x30\x2e\x34\x33\x34\x2d\x30\x2e\x39\x31\x36\x63\x2d\x38\x2e\ -\x30\x31\x36\x2c\x30\x2e\x36\x31\x2d\x31\x32\x2e\x34\x39\x37\x2c\ -\x30\x2e\x39\x38\x38\x2d\x31\x33\x2e\x34\x34\x34\x2c\x31\x2e\x31\ -\x33\x33\x6c\x2d\x30\x2e\x39\x34\x2c\x30\x2e\x32\x34\x63\x2d\x30\ -\x2e\x33\x35\x34\x2c\x30\x2e\x30\x38\x2d\x30\x2e\x35\x36\x33\x2d\ -\x30\x2e\x30\x35\x36\x2d\x30\x2e\x36\x32\x36\x2d\x30\x2e\x34\x30\ -\x39\x6c\x2d\x30\x2e\x35\x37\x38\x2d\x32\x2e\x35\x33\x0d\x0a\x09\ -\x09\x63\x2d\x30\x2e\x30\x36\x34\x2d\x30\x2e\x33\x35\x33\x2c\x30\ -\x2e\x30\x34\x2d\x30\x2e\x36\x32\x36\x2c\x30\x2e\x33\x31\x33\x2d\ -\x30\x2e\x38\x31\x38\x63\x2d\x30\x2e\x30\x34\x38\x2c\x30\x2e\x31\ -\x36\x2c\x30\x2e\x33\x30\x36\x2d\x30\x2e\x31\x36\x2c\x31\x2e\x30\ -\x36\x31\x2d\x30\x2e\x39\x36\x35\x63\x30\x2e\x33\x33\x37\x2d\x30\ -\x2e\x33\x33\x36\x2c\x30\x2e\x38\x33\x35\x2d\x30\x2e\x39\x34\x37\ -\x2c\x31\x2e\x34\x39\x34\x2d\x31\x2e\x38\x33\x6c\x31\x2e\x38\x33\ -\x31\x2d\x32\x2e\x38\x31\x39\x68\x2d\x35\x2e\x37\x38\x33\x0d\x0a\ -\x09\x09\x63\x2d\x30\x2e\x33\x36\x39\x2c\x30\x2d\x30\x2e\x35\x32\ -\x39\x2d\x30\x2e\x31\x37\x37\x2d\x30\x2e\x34\x38\x31\x2d\x30\x2e\ -\x35\x33\x6c\x30\x2e\x34\x35\x38\x2d\x32\x2e\x34\x38\x31\x63\x30\ -\x2e\x30\x34\x38\x2d\x30\x2e\x33\x35\x34\x2c\x30\x2e\x32\x35\x37\ -\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x36\x32\x36\x2d\x30\x2e\x35\x33\ -\x68\x38\x2e\x30\x34\x38\x76\x2d\x32\x2e\x37\x39\x35\x68\x2d\x36\ -\x2e\x37\x39\x35\x0d\x0a\x09\x09\x63\x2d\x30\x2e\x33\x36\x39\x2c\ -\x30\x2d\x30\x2e\x35\x33\x2d\x30\x2e\x31\x37\x37\x2d\x30\x2e\x34\ -\x38\x31\x2d\x30\x2e\x35\x33\x6c\x30\x2e\x34\x33\x34\x2d\x32\x2e\ -\x34\x35\x38\x63\x30\x2e\x30\x34\x38\x2d\x30\x2e\x33\x35\x33\x2c\ -\x30\x2e\x32\x36\x35\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x36\x35\x2d\ -\x30\x2e\x35\x33\x68\x36\x2e\x31\x39\x32\x76\x2d\x32\x2e\x35\x33\ -\x63\x30\x2d\x30\x2e\x33\x36\x39\x2c\x30\x2e\x31\x37\x37\x2d\x30\ -\x2e\x35\x33\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x34\x38\x32\x0d\x0a\ -\x09\x09\x6c\x32\x2e\x36\x37\x34\x2c\x30\x2e\x34\x35\x38\x63\x30\ -\x2e\x33\x35\x34\x2c\x30\x2e\x30\x34\x38\x2c\x30\x2e\x35\x33\x2c\ -\x30\x2e\x32\x35\x37\x2c\x30\x2e\x35\x33\x2c\x30\x2e\x36\x32\x36\ -\x76\x31\x2e\x39\x32\x37\x68\x37\x2e\x30\x33\x36\x63\x30\x2e\x33\ -\x36\x39\x2c\x30\x2c\x30\x2e\x35\x32\x39\x2c\x30\x2e\x31\x37\x37\ -\x2c\x30\x2e\x34\x38\x31\x2c\x30\x2e\x35\x33\x6c\x2d\x30\x2e\x34\ -\x35\x38\x2c\x32\x2e\x34\x35\x38\x0d\x0a\x09\x09\x63\x2d\x30\x2e\ -\x30\x34\x38\x2c\x30\x2e\x33\x35\x34\x2d\x30\x2e\x32\x35\x37\x2c\ -\x30\x2e\x35\x33\x2d\x30\x2e\x36\x32\x36\x2c\x30\x2e\x35\x33\x68\ -\x2d\x36\x2e\x34\x33\x34\x76\x32\x2e\x37\x39\x35\x68\x38\x2e\x37\ -\x34\x36\x43\x31\x30\x36\x2e\x39\x38\x39\x2c\x31\x35\x2e\x31\x30\ -\x35\x2c\x31\x30\x37\x2e\x31\x34\x39\x2c\x31\x35\x2e\x32\x38\x32\ -\x2c\x31\x30\x37\x2e\x31\x30\x32\x2c\x31\x35\x2e\x36\x33\x36\x7a\ -\x22\x2f\x3e\x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\ -\x3d\x22\x23\x46\x46\x46\x46\x46\x46\x22\x20\x64\x3d\x22\x4d\x31\ -\x31\x35\x2e\x31\x39\x37\x2c\x31\x31\x2e\x33\x37\x31\x63\x30\x2e\ -\x33\x36\x39\x2c\x30\x2c\x30\x2e\x37\x30\x37\x2c\x30\x2e\x31\x30\ -\x35\x2c\x31\x2e\x30\x31\x32\x2c\x30\x2e\x33\x31\x33\x6c\x31\x2e\ -\x33\x30\x32\x2c\x30\x2e\x38\x39\x32\x63\x30\x2e\x33\x30\x35\x2c\ -\x30\x2e\x32\x30\x39\x2c\x30\x2e\x34\x32\x35\x2c\x30\x2e\x35\x30\ -\x36\x2c\x30\x2e\x33\x36\x31\x2c\x30\x2e\x38\x39\x31\x0d\x0a\x09\ -\x09\x63\x2d\x30\x2e\x33\x37\x2c\x32\x2e\x35\x30\x36\x2d\x31\x2e\ -\x31\x35\x36\x2c\x35\x2e\x30\x30\x34\x2d\x32\x2e\x33\x36\x31\x2c\ -\x37\x2e\x34\x39\x34\x63\x2d\x31\x2e\x31\x38\x39\x2c\x32\x2e\x34\ -\x37\x33\x2d\x32\x2e\x34\x39\x38\x2c\x34\x2e\x32\x35\x36\x2d\x33\ -\x2e\x39\x32\x38\x2c\x35\x2e\x33\x34\x39\x63\x2d\x30\x2e\x32\x35\ -\x37\x2c\x30\x2e\x32\x30\x38\x2d\x30\x2e\x34\x39\x38\x2c\x30\x2e\ -\x31\x37\x37\x2d\x30\x2e\x37\x32\x33\x2d\x30\x2e\x30\x39\x37\x6c\ -\x2d\x31\x2e\x37\x33\x34\x2d\x32\x2e\x31\x39\x32\x0d\x0a\x09\x09\ -\x63\x2d\x30\x2e\x32\x32\x36\x2d\x30\x2e\x32\x37\x32\x2d\x30\x2e\ -\x32\x30\x31\x2d\x30\x2e\x35\x32\x32\x2c\x30\x2e\x30\x37\x32\x2d\ -\x30\x2e\x37\x34\x37\x63\x32\x2e\x30\x38\x38\x2d\x31\x2e\x34\x31\ -\x33\x2c\x33\x2e\x35\x36\x35\x2d\x33\x2e\x38\x36\x33\x2c\x34\x2e\ -\x34\x33\x33\x2d\x37\x2e\x33\x34\x39\x63\x30\x2e\x31\x36\x31\x2d\ -\x30\x2e\x35\x39\x34\x2c\x30\x2e\x30\x30\x39\x2d\x30\x2e\x38\x39\ -\x32\x2d\x30\x2e\x34\x35\x37\x2d\x30\x2e\x38\x39\x32\x68\x2d\x33\ -\x2e\x33\x35\x0d\x0a\x09\x09\x63\x2d\x30\x2e\x33\x36\x39\x2c\x30\ -\x2d\x30\x2e\x35\x33\x2d\x30\x2e\x31\x37\x37\x2d\x30\x2e\x34\x38\ -\x31\x2d\x30\x2e\x35\x33\x6c\x30\x2e\x34\x35\x37\x2d\x32\x2e\x34\ -\x38\x31\x63\x30\x2e\x30\x34\x39\x2d\x30\x2e\x33\x35\x33\x2c\x30\ -\x2e\x32\x35\x37\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x36\x32\x37\x2d\ -\x30\x2e\x35\x33\x4c\x31\x31\x35\x2e\x31\x39\x37\x2c\x31\x31\x2e\ -\x33\x37\x31\x7a\x20\x4d\x31\x32\x36\x2e\x38\x31\x31\x2c\x31\x30\ -\x2e\x31\x39\x0d\x0a\x09\x09\x63\x30\x2e\x32\x30\x39\x2d\x30\x2e\ -\x32\x37\x33\x2c\x30\x2e\x34\x34\x32\x2d\x30\x2e\x32\x38\x31\x2c\ -\x30\x2e\x36\x39\x39\x2d\x30\x2e\x30\x32\x34\x6c\x31\x2e\x39\x37\ -\x36\x2c\x32\x63\x30\x2e\x32\x35\x37\x2c\x30\x2e\x32\x35\x37\x2c\ -\x30\x2e\x32\x34\x31\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x30\x34\x38\ -\x2c\x30\x2e\x38\x31\x39\x63\x2d\x31\x2e\x34\x33\x2c\x31\x2e\x33\ -\x39\x37\x2d\x33\x2e\x30\x32\x31\x2c\x32\x2e\x37\x36\x33\x2d\x34\ -\x2e\x37\x37\x31\x2c\x34\x2e\x30\x39\x36\x0d\x0a\x09\x09\x63\x31\ -\x2e\x38\x36\x32\x2c\x32\x2e\x38\x34\x33\x2c\x34\x2e\x30\x38\x37\ -\x2c\x34\x2e\x38\x32\x37\x2c\x36\x2e\x36\x37\x34\x2c\x35\x2e\x39\ -\x35\x32\x63\x30\x2e\x33\x35\x33\x2c\x30\x2e\x31\x34\x35\x2c\x30\ -\x2e\x34\x30\x31\x2c\x30\x2e\x33\x33\x37\x2c\x30\x2e\x31\x34\x35\ -\x2c\x30\x2e\x35\x37\x38\x4c\x31\x32\x39\x2e\x31\x2c\x32\x35\x2e\ -\x39\x63\x2d\x30\x2e\x32\x35\x37\x2c\x30\x2e\x32\x34\x2d\x30\x2e\ -\x35\x33\x38\x2c\x30\x2e\x32\x36\x35\x2d\x30\x2e\x38\x34\x33\x2c\ -\x30\x2e\x30\x37\x32\x0d\x0a\x09\x09\x63\x2d\x32\x2e\x32\x34\x39\ -\x2d\x31\x2e\x31\x37\x33\x2d\x34\x2e\x32\x39\x37\x2d\x33\x2e\x34\ -\x31\x34\x2d\x36\x2e\x31\x34\x35\x2d\x36\x2e\x37\x32\x33\x76\x35\ -\x2e\x30\x31\x32\x63\x30\x2c\x30\x2e\x39\x39\x36\x2d\x30\x2e\x31\ -\x30\x34\x2c\x31\x2e\x37\x36\x37\x2d\x30\x2e\x33\x31\x33\x2c\x32\ -\x2e\x33\x31\x33\x63\x2d\x30\x2e\x32\x30\x38\x2c\x30\x2e\x35\x34\ -\x36\x2d\x30\x2e\x35\x37\x38\x2c\x30\x2e\x39\x37\x32\x2d\x31\x2e\ -\x31\x30\x37\x2c\x31\x2e\x32\x37\x37\x0d\x0a\x09\x09\x63\x2d\x30\ -\x2e\x39\x33\x33\x2c\x30\x2e\x35\x32\x39\x2d\x32\x2e\x32\x35\x38\ -\x2c\x30\x2e\x37\x38\x37\x2d\x33\x2e\x39\x37\x36\x2c\x30\x2e\x37\ -\x37\x31\x63\x2d\x30\x2e\x34\x38\x32\x2c\x30\x2e\x30\x31\x36\x2d\ -\x30\x2e\x37\x35\x36\x2d\x30\x2e\x31\x35\x33\x2d\x30\x2e\x38\x31\ -\x39\x2d\x30\x2e\x35\x30\x36\x6c\x2d\x30\x2e\x36\x30\x33\x2d\x32\ -\x2e\x38\x32\x63\x2d\x30\x2e\x30\x36\x34\x2d\x30\x2e\x33\x35\x33\ -\x2c\x30\x2e\x30\x38\x2d\x30\x2e\x35\x32\x39\x2c\x30\x2e\x34\x33\ -\x34\x2d\x30\x2e\x35\x32\x39\x0d\x0a\x09\x09\x63\x30\x2e\x31\x37\ -\x37\x2c\x30\x2e\x30\x31\x36\x2c\x30\x2e\x38\x36\x37\x2c\x30\x2e\ -\x30\x32\x33\x2c\x32\x2e\x30\x37\x32\x2c\x30\x2e\x30\x32\x33\x63\ -\x30\x2e\x33\x38\x36\x2c\x30\x2c\x30\x2e\x35\x37\x38\x2d\x30\x2e\ -\x31\x38\x34\x2c\x30\x2e\x35\x37\x38\x2d\x30\x2e\x35\x35\x34\x56\ -\x36\x2e\x32\x38\x37\x63\x30\x2d\x30\x2e\x33\x37\x2c\x30\x2e\x31\ -\x37\x37\x2d\x30\x2e\x35\x33\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x34\ -\x38\x32\x6c\x32\x2e\x36\x37\x34\x2c\x30\x2e\x34\x35\x38\x0d\x0a\ -\x09\x09\x63\x30\x2e\x33\x35\x34\x2c\x30\x2e\x30\x34\x38\x2c\x30\ -\x2e\x35\x33\x2c\x30\x2e\x32\x35\x37\x2c\x30\x2e\x35\x33\x2c\x30\ -\x2e\x36\x32\x36\x76\x34\x2e\x37\x39\x35\x63\x30\x2e\x32\x34\x31\ -\x2c\x30\x2e\x37\x35\x35\x2c\x30\x2e\x35\x37\x2c\x31\x2e\x36\x31\ -\x34\x2c\x30\x2e\x39\x38\x38\x2c\x32\x2e\x35\x37\x38\x43\x31\x32\ -\x34\x2e\x37\x33\x39\x2c\x31\x32\x2e\x37\x33\x36\x2c\x31\x32\x35\ -\x2e\x39\x37\x36\x2c\x31\x31\x2e\x33\x37\x39\x2c\x31\x32\x36\x2e\ -\x38\x31\x31\x2c\x31\x30\x2e\x31\x39\x7a\x22\x2f\x3e\x0d\x0a\x09\ -\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\ -\x46\x46\x46\x22\x20\x64\x3d\x22\x4d\x31\x34\x30\x2e\x38\x35\x38\ -\x2c\x36\x2e\x35\x35\x32\x63\x30\x2e\x33\x30\x35\x2d\x30\x2e\x31\ -\x34\x35\x2c\x30\x2e\x35\x36\x32\x2d\x30\x2e\x30\x37\x32\x2c\x30\ -\x2e\x37\x37\x31\x2c\x30\x2e\x32\x31\x37\x6c\x31\x2e\x34\x39\x34\ -\x2c\x32\x2e\x31\x39\x33\x63\x30\x2e\x32\x30\x39\x2c\x30\x2e\x32\ -\x38\x39\x2c\x30\x2e\x31\x36\x2c\x30\x2e\x34\x38\x32\x2d\x30\x2e\ -\x31\x34\x35\x2c\x30\x2e\x35\x37\x38\x0d\x0a\x09\x09\x63\x2d\x30\ -\x2e\x39\x36\x34\x2c\x30\x2e\x33\x37\x2d\x32\x2e\x33\x36\x39\x2c\ -\x30\x2e\x37\x36\x33\x2d\x34\x2e\x32\x31\x37\x2c\x31\x2e\x31\x38\ -\x31\x63\x2d\x30\x2e\x36\x31\x2c\x30\x2e\x31\x32\x39\x2d\x30\x2e\ -\x39\x31\x35\x2c\x30\x2e\x34\x32\x36\x2d\x30\x2e\x39\x31\x35\x2c\ -\x30\x2e\x38\x39\x32\x76\x32\x2e\x30\x39\x36\x68\x34\x2e\x39\x31\ -\x35\x63\x30\x2e\x33\x36\x39\x2c\x30\x2c\x30\x2e\x35\x32\x31\x2c\ -\x30\x2e\x31\x37\x37\x2c\x30\x2e\x34\x35\x38\x2c\x30\x2e\x35\x33\ -\x0d\x0a\x09\x09\x6c\x2d\x30\x2e\x34\x31\x2c\x32\x2e\x33\x31\x33\ -\x63\x2d\x30\x2e\x30\x36\x34\x2c\x30\x2e\x33\x35\x34\x2d\x30\x2e\ -\x32\x38\x31\x2c\x30\x2e\x35\x33\x2d\x30\x2e\x36\x35\x2c\x30\x2e\ -\x35\x33\x68\x2d\x34\x2e\x33\x31\x33\x76\x33\x2e\x36\x31\x34\x63\ -\x30\x2c\x30\x2e\x34\x36\x37\x2c\x30\x2e\x32\x32\x35\x2c\x30\x2e\ -\x36\x35\x38\x2c\x30\x2e\x36\x37\x35\x2c\x30\x2e\x35\x37\x38\x6c\ -\x34\x2e\x32\x34\x2d\x30\x2e\x37\x37\x31\x0d\x0a\x09\x09\x63\x30\ -\x2e\x33\x35\x34\x2d\x30\x2e\x30\x36\x33\x2c\x30\x2e\x34\x39\x38\ -\x2c\x30\x2e\x30\x38\x31\x2c\x30\x2e\x34\x33\x34\x2c\x30\x2e\x34\ -\x33\x35\x6c\x2d\x30\x2e\x34\x38\x31\x2c\x32\x2e\x34\x33\x34\x63\ -\x2d\x30\x2e\x30\x36\x34\x2c\x30\x2e\x33\x35\x33\x2d\x30\x2e\x33\ -\x34\x36\x2c\x30\x2e\x35\x37\x38\x2d\x30\x2e\x38\x34\x34\x2c\x30\ -\x2e\x36\x37\x35\x63\x2d\x32\x2e\x32\x39\x37\x2c\x30\x2e\x34\x38\ -\x31\x2d\x34\x2e\x30\x32\x33\x2c\x30\x2e\x38\x37\x35\x2d\x35\x2e\ -\x31\x38\x31\x2c\x31\x2e\x31\x38\x0d\x0a\x09\x09\x63\x2d\x31\x2e\ -\x31\x35\x36\x2c\x30\x2e\x32\x38\x39\x2d\x31\x2e\x38\x35\x34\x2c\ -\x30\x2e\x35\x30\x36\x2d\x32\x2e\x30\x39\x36\x2c\x30\x2e\x36\x35\ -\x63\x2d\x30\x2e\x32\x32\x36\x2c\x30\x2e\x31\x31\x33\x2d\x30\x2e\ -\x33\x37\x2d\x30\x2e\x30\x30\x38\x2d\x30\x2e\x34\x33\x34\x2d\x30\ -\x2e\x33\x36\x31\x6c\x2d\x30\x2e\x35\x35\x35\x2d\x33\x2e\x30\x31\ -\x32\x63\x2d\x30\x2e\x30\x36\x34\x2d\x30\x2e\x33\x35\x33\x2c\x30\ -\x2e\x30\x34\x39\x2d\x30\x2e\x36\x35\x38\x2c\x30\x2e\x33\x33\x38\ -\x2d\x30\x2e\x39\x31\x35\x0d\x0a\x09\x09\x63\x30\x2e\x32\x32\x35\ -\x2c\x30\x2e\x30\x39\x37\x2c\x30\x2e\x33\x33\x37\x2d\x30\x2e\x32\ -\x34\x31\x2c\x30\x2e\x33\x33\x37\x2d\x31\x2e\x30\x31\x33\x56\x39\ -\x2e\x38\x35\x33\x63\x30\x2d\x30\x2e\x38\x33\x35\x2c\x30\x2e\x34\ -\x37\x34\x2d\x31\x2e\x33\x35\x37\x2c\x31\x2e\x34\x32\x32\x2d\x31\ -\x2e\x35\x36\x36\x43\x31\x33\x37\x2e\x34\x36\x39\x2c\x37\x2e\x39\ -\x30\x31\x2c\x31\x33\x39\x2e\x31\x38\x38\x2c\x37\x2e\x33\x32\x33\ -\x2c\x31\x34\x30\x2e\x38\x35\x38\x2c\x36\x2e\x35\x35\x32\x7a\x0d\ -\x0a\x09\x09\x20\x4d\x31\x35\x33\x2e\x33\x33\x39\x2c\x37\x2e\x33\ -\x34\x37\x63\x30\x2e\x38\x38\x34\x2c\x30\x2c\x31\x2e\x33\x32\x35\ -\x2c\x30\x2e\x34\x34\x32\x2c\x31\x2e\x33\x32\x35\x2c\x31\x2e\x33\ -\x32\x35\x56\x32\x31\x2e\x34\x39\x63\x30\x2c\x31\x2e\x36\x35\x34\ -\x2d\x30\x2e\x34\x31\x38\x2c\x32\x2e\x37\x33\x39\x2d\x31\x2e\x32\ -\x35\x33\x2c\x33\x2e\x32\x35\x33\x63\x2d\x30\x2e\x37\x37\x31\x2c\ -\x30\x2e\x34\x38\x31\x2d\x31\x2e\x37\x37\x34\x2c\x30\x2e\x37\x32\ -\x34\x2d\x33\x2e\x30\x31\x32\x2c\x30\x2e\x37\x32\x34\x0d\x0a\x09\ -\x09\x63\x2d\x30\x2e\x34\x38\x31\x2c\x30\x2d\x30\x2e\x37\x36\x33\ -\x2d\x30\x2e\x31\x37\x38\x2d\x30\x2e\x38\x34\x33\x2d\x30\x2e\x35\ -\x33\x31\x6c\x2d\x30\x2e\x35\x37\x38\x2d\x32\x2e\x36\x32\x36\x63\ -\x2d\x30\x2e\x30\x38\x31\x2d\x30\x2e\x33\x35\x33\x2c\x30\x2e\x30\ -\x35\x36\x2d\x30\x2e\x35\x32\x31\x2c\x30\x2e\x34\x30\x39\x2d\x30\ -\x2e\x35\x30\x36\x63\x2d\x30\x2e\x30\x38\x2c\x30\x2c\x30\x2e\x33\ -\x35\x34\x2c\x30\x2c\x31\x2e\x33\x30\x31\x2c\x30\x0d\x0a\x09\x09\ -\x63\x30\x2e\x32\x38\x39\x2c\x30\x2c\x30\x2e\x34\x33\x34\x2d\x30\ -\x2e\x31\x34\x35\x2c\x30\x2e\x34\x33\x34\x2d\x30\x2e\x34\x33\x34\ -\x76\x2d\x39\x2e\x38\x35\x34\x63\x30\x2d\x30\x2e\x34\x36\x36\x2d\ -\x30\x2e\x32\x33\x32\x2d\x30\x2e\x36\x39\x39\x2d\x30\x2e\x36\x39\ -\x38\x2d\x30\x2e\x36\x39\x39\x68\x2d\x31\x2e\x37\x38\x33\x63\x2d\ -\x30\x2e\x34\x36\x36\x2c\x30\x2d\x30\x2e\x36\x39\x38\x2c\x30\x2e\ -\x32\x33\x33\x2d\x30\x2e\x36\x39\x38\x2c\x30\x2e\x36\x39\x39\x56\ -\x32\x38\x2e\x30\x32\x0d\x0a\x09\x09\x63\x30\x2c\x30\x2e\x33\x37\ -\x2d\x30\x2e\x31\x37\x38\x2c\x30\x2e\x35\x33\x31\x2d\x30\x2e\x35\ -\x33\x2c\x30\x2e\x34\x38\x32\x6c\x2d\x32\x2e\x34\x38\x31\x2d\x30\ -\x2e\x34\x35\x37\x63\x2d\x30\x2e\x33\x35\x34\x2d\x30\x2e\x30\x34\ -\x39\x2d\x30\x2e\x35\x33\x2d\x30\x2e\x32\x35\x38\x2d\x30\x2e\x35\ -\x33\x2d\x30\x2e\x36\x32\x37\x56\x38\x2e\x36\x37\x32\x63\x30\x2d\ -\x30\x2e\x38\x38\x33\x2c\x30\x2e\x34\x34\x31\x2d\x31\x2e\x33\x32\ -\x35\x2c\x31\x2e\x33\x32\x35\x2d\x31\x2e\x33\x32\x35\x48\x31\x35\ -\x33\x2e\x33\x33\x39\x7a\x22\x0d\x0a\x09\x09\x2f\x3e\x0d\x0a\x3c\ -\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ -\x00\x00\x02\x17\ -\x00\ -\x00\x07\x01\x78\x9c\xe5\x55\xcd\x8a\xdb\x30\x10\x7e\x95\x41\xb9\ -\xec\x82\x63\xcb\x3f\xb2\xa2\x10\x07\xb6\x21\x39\xa5\xf4\xd0\x5b\ -\x2f\xc5\xbb\x56\x6c\xb3\x8e\x1d\x6c\x25\xce\x3e\x44\xe9\xa9\xb7\ -\xbe\x5e\xa1\x8f\xb1\x23\x39\xbb\x4d\x42\x02\x66\x61\x0f\x4b\x65\ -\xf4\x33\x9a\x6f\x46\xf3\x7d\x12\x78\xd2\xec\x52\xd8\xaf\x8b\xb2\ -\x89\x48\xa6\xd4\x66\xec\x38\x6d\xdb\xda\xad\x6f\x57\x75\xea\x78\ -\x94\x52\x07\x11\xa4\x83\x8c\xf7\x45\x5e\x3e\x5e\x02\xba\x42\x08\ -\xc7\x78\x09\xb4\x79\xa2\xb2\x88\x08\x4a\x09\x64\x32\x4f\x33\x15\ -\x91\x50\x1b\xbb\x5c\xb6\x9f\xaa\x7d\x44\x28\x50\x40\x37\x98\xdd\ -\x55\x5e\x14\x11\x29\xab\x52\x92\xe9\x24\x91\xab\x66\x3a\xa9\xe5\ -\x83\x82\x3c\x89\xc8\x26\x56\xd9\x77\xc4\xe8\x18\x02\x4f\x66\xbc\ -\x9a\xde\x99\x4e\x9c\x2e\x3e\x85\x6a\x13\x3f\xe4\x0a\x03\x5c\x02\ -\xaa\x8e\xcb\x66\x55\xd5\xeb\x88\x98\x65\x11\x2b\x79\x83\x25\xdc\ -\x02\xd4\x95\xea\x8c\x80\x51\xf0\x29\xbd\x25\x87\xc3\xbb\xa2\x06\ -\x0b\xd3\xc8\xdb\xd3\xf5\xae\x7c\x1d\x37\x8f\x86\xf2\x7d\x3a\xd4\ -\xeb\x17\x5d\xda\x2c\x57\x5a\x98\x6d\x23\xc1\xe8\x3b\xce\x6a\xb9\ -\xc2\xd2\x0e\xd2\x20\x67\x74\xe1\xa8\x83\x34\x73\x3d\x47\x64\x5b\ -\x17\x37\x83\x43\x2a\xac\x62\x3a\xd1\x70\x93\xff\xef\xcf\xdf\x7f\ -\x7e\xfc\xea\xd2\x0f\xeb\x6d\x21\x23\x22\x77\xb2\xac\x92\x84\x40\ -\xa3\x9e\xb4\xad\x5d\xe3\xc1\x9c\xe9\xef\x2a\x5f\xef\x84\xb0\x4b\ -\x19\x78\x42\x13\x3e\x96\x0a\x8f\xfb\x4c\x2d\x36\xa2\x4b\xcf\xfd\ -\x37\xd3\x65\xd7\xd1\xfe\x06\x86\xfc\xdb\xee\xcb\x35\xd7\xd5\x23\ -\x96\xd9\xfe\x88\xb9\x21\x13\x82\xfb\xbe\xe0\x34\x94\x43\xd7\x3f\ -\xcb\x17\x08\x5b\x1c\x35\x8e\xd9\x6d\x7a\xd2\x98\x3e\xed\x55\xc6\ -\x59\xb5\xbe\xcf\x4b\x99\xc0\xd7\x2c\xde\xc8\x3e\x72\x06\xf3\xbb\ -\xf9\xa2\x27\xbb\x5e\xd5\x5c\x90\xda\xa3\x4b\x7c\x5d\x2f\x53\x38\ -\x33\x86\x1d\x0a\x18\x09\x6e\xfb\xae\x45\x71\x11\xa0\xf8\xa1\x45\ -\x67\x7a\x1f\x37\x0e\x00\x44\x2f\x75\xe0\xe1\x46\x5e\x79\x7e\xd9\ -\xc5\x45\x0f\x76\x0b\x81\xdf\xdd\x15\x76\x1c\xf8\x59\xf5\xc1\x31\ -\x5b\xdf\x66\xd8\x03\x71\xd2\xc2\x0b\xfc\xb8\x85\xd0\x19\xb7\x5c\ -\x9b\x71\x60\x76\xe0\x5b\x26\x18\xb9\xe8\x1d\xc3\xc5\xb8\xa8\xc1\ -\xe1\x03\x43\x08\x18\x17\x37\x38\x3e\x33\x41\x58\x4e\xe7\x32\xf9\ -\xde\x83\xb0\xfb\xdf\x31\xf6\x3e\x02\x63\x27\x3d\xeb\xf8\x67\x9b\ -\x3e\x03\xf1\x7a\xec\xab\ -" - -qt_resource_name = b"\ -\x00\x03\ -\x00\x00\x70\x37\ -\x00\x69\ -\x00\x6d\x00\x67\ -\x00\x13\ -\x0a\x25\xf2\x27\ -\x00\x6c\ -\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x68\x00\x6f\x00\x72\x00\x69\x00\x7a\x00\x6f\x00\x6e\x00\x74\x00\x61\x00\x6c\x00\x2e\x00\x73\ -\x00\x76\x00\x67\ -\x00\x06\ -\x03\x32\x4a\x27\ -\x80\xcc\ -\x66\x6f\x00\x2e\x00\x73\x00\x76\x00\x67\ -" - -qt_resource_struct_v1 = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ -\x00\x00\x00\x38\x00\x01\x00\x00\x00\x01\x00\x00\x22\x42\ -\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -" - -qt_resource_struct_v2 = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x38\x00\x01\x00\x00\x00\x01\x00\x00\x22\x42\ -\x00\x00\x01\x7e\x9b\x06\x48\x80\ -\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x7e\x9b\x58\x78\xfc\ -" - -qt_version = [int(v) for v in QtCore.qVersion().split('.')] -if qt_version < [5, 8, 0]: - rcc_version = 1 - qt_resource_struct = qt_resource_struct_v1 -else: - rcc_version = 2 - qt_resource_struct = qt_resource_struct_v2 - -def qInitResources(): - QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/Util/Urls.py b/Util/Urls.py deleted file mode 100644 index bef4a0d3..00000000 --- a/Util/Urls.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Urls.py -@Date :2023/02/08 18:14:47 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/02/08 18:14:47 - Create Urls from https://johnserf-seed.github.io/DouyinApiDoc/APIdocV1.0.html -------------------------------------------------- -''' - -import Util - - -class Urls: - def __init__(self): - ######################################### WEB ######################################### - # 首页推荐 - self.TAB_FEED = 'https://www.douyin.com/aweme/v1/web/tab/feed/?' - - # 用户短信息(给多少个用户secid就返回多少的用户信息) - self.USER_SHORT_INFO = 'https://www.douyin.com/aweme/v1/web/im/user/info/?' - - # 用户详细信息 - self.USER_DETAIL = 'https://www.douyin.com/aweme/v1/web/user/profile/other/?' - - # 作品基本 - self.BASE_AWEME = 'https://www.douyin.com/aweme/v1/web/aweme/' - - # 用户作品 - self.USER_POST = 'https://www.douyin.com/aweme/v1/web/aweme/post/?' - - # 作品信息 - self.POST_DETAIL = 'https://www.douyin.com/aweme/v1/web/aweme/detail/?' - - # 用户喜欢A - self.USER_FAVORITE_A = 'https://www.douyin.com/aweme/v1/web/aweme/favorite/?' - - # 用户喜欢B - self.USER_FAVORITE_B = 'https://www.iesdouyin.com/web/api/v2/aweme/like/?' - - # 用户历史 - self.USER_HISTORY = 'https://www.douyin.com/aweme/v1/web/history/read/?' - - # 用户收藏 - self.USER_COLLECTION = 'https://www.douyin.com/aweme/v1/web/aweme/listcollection/?' - - # 用户评论 - self.COMMENT = 'https://www.douyin.com/aweme/v1/web/comment/list/?' - - # 首页朋友作品 - self.FRIEND_FEED = 'https://www.douyin.com/aweme/v1/web/familiar/feed/?' - - # 关注用户作品 - self.FOLLOW_FEED = 'https://www.douyin.com/aweme/v1/web/follow/feed/?' - - # 相关推荐 - self.RELATED = 'https://www.douyin.com/aweme/v1/web/aweme/related/?' - - # 直播信息接口 - self.LIVE = 'https://live.douyin.com/webcast/room/web/enter/?' - - # SSO登录 - self.SSO_LOGIN_GET_QR = 'https://sso.douyin.com/get_qrcode/?' - - self.SSO_LOGIN_CHECK_QR = 'https://sso.douyin.com/check_qrconnect/?' - - self.SSO_LOGIN_CHECK_LOGIN = 'https://sso.douyin.com/check_login/?' # set-Cookie有passport_csrf_token - - self.SSO_LOGIN_REDIRECT = 'https://www.douyin.com/login/?' - - self.SSO_LOGIN_CALLBACK = 'https://www.douyin.com/passport/sso/login/callback/?' - - # 作品评论 - self.POST_COMMENT = 'https://www.douyin.com/aweme/v1/web/comment/list/?' - - # 回复评论 - self.POST_COMMENT_PUBLISH = 'https://www.douyin.com/aweme/v1/web/comment/publish?' - - # 删除评论 - self.POST_COMMENT_DELETE = 'https://www.douyin.com/aweme/v1/web/comment/delete/?' - - # 点赞评论 - self.POST_COMMENT_DIGG = 'https://www.douyin.com/aweme/v1/web/comment/digg?' # 1点赞 2取消点赞 3踩 4取消踩 - - # 展开评论 - self.POST_COMMENT_REPLY = 'https://www.douyin.com/aweme/v1/web/comment/list/reply/?' - - - # 消息通知 - self.NOTICE = 'https://www.douyin.com/aweme/v1/web/notice/?' - - - # X-Bogus Path - # self.GET_XB_PATH = 'http://127.0.0.1:8889/xg/path?url=' - - # X-Bogus Login - # self.GET_XB_LOGIN = 'http://47.115.200.238/login' - - # X-Bogus Register - # self.GET_XB_REGISTER = 'http://47.115.200.238/register' - - # X-Bogus Token - # self.GET_XB_TOKEN = 'http://47.115.200.238/token' - ####################################################################################### - - ######################################### APP ######################################### - # X-Gorgon Path - # self.GET_XG_LOGIN = 'http://47.115.200.238/xog/path?url=' - - ####################################################################################### diff --git a/Util/XB.py b/Util/XB.py deleted file mode 100644 index 514e5f66..00000000 --- a/Util/XB.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:XB.py -@Date :2023/02/09 00:29:30 -@Author :JohnserfSeed -@version :0.0.1 -@License :Apache License 2.0 -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/02/09 00:29:30 - Create XBogus class -2023/06/07 17:26:02 - Refactor the XB algorithm using Python. -------------------------------------------------- -''' - -import time -import hashlib - - -class XBogus: - def __init__(self) -> None: - - self.Array = [ - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, 10, 11, 12, 13, 14, 15 - ] - self.character = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=" - - - def md5_str_to_array(self, md5_str): - """ - 将字符串使用md5哈希算法转换为整数数组。 - Convert a string to an array of integers using the md5 hashing algorithm. - """ - if isinstance(md5_str, str) and len(md5_str) > 32: - return [ord(char) for char in md5_str] - else: - array = [] - idx = 0 - while idx < len(md5_str): - array.append((self.Array[ord(md5_str[idx])] << 4) | self.Array[ord(md5_str[idx + 1])]) - idx += 2 - return array - - - def md5_encrypt(self, url_path): - """ - 使用多轮md5哈希算法对URL路径进行加密。 - Encrypt the URL path using multiple rounds of md5 hashing. - """ - hashed_url_path = self.md5_str_to_array(self.md5(self.md5_str_to_array(self.md5(url_path)))) - return hashed_url_path - - - def md5(self, input_data): - """ - 计算输入数据的md5哈希值。 - Calculate the md5 hash value of the input data. - """ - if isinstance(input_data, str): - array = self.md5_str_to_array(input_data) - elif isinstance(input_data, list): - array = input_data - else: - raise ValueError("Invalid input type. Expected str or list.") - - md5_hash = hashlib.md5() - md5_hash.update(bytes(array)) - return md5_hash.hexdigest() - - - def encoding_conversion(self, a, b, c, e, d, t, f, r, n, o, i, _, x, u, s, l, v, h, p): - """ - 第一次编码转换。 - Perform encoding conversion. - """ - y = [a] - y.append(int(i)) - y.extend([b, _, c, x, e, u, d, s, t, l, f, v, r, h, n, p, o]) - re = bytes(y).decode('ISO-8859-1') - return re - - - def encoding_conversion2(self, a, b, c): - """ - 第三次编码转换。 - Perform an encoding conversion on the given input values and return the result. - """ - return chr(a) + chr(b) + c - - - def rc4_encrypt(self, key, data): - """ - 使用RC4算法对数据进行加密。 - Encrypt data using the RC4 algorithm. - """ - S = list(range(256)) - j = 0 - encrypted_data = bytearray() - - # 初始化 S 盒 - # Initialize the S box - for i in range(256): - j = (j + S[i] + key[i % len(key)]) % 256 - S[i], S[j] = S[j], S[i] - - # 生成密文 - # Generate the ciphertext - i = j = 0 - for byte in data: - i = (i + 1) % 256 - j = (j + S[i]) % 256 - S[i], S[j] = S[j], S[i] - encrypted_byte = byte ^ S[(S[i] + S[j]) % 256] - encrypted_data.append(encrypted_byte) - - return encrypted_data - - - def calculation(self, a1, a2, a3): - """ - 对给定的输入值执行位运算计算,并返回结果。 - Perform a calculation using bitwise operations on the given input values and return the result. - """ - x1 = (a1 & 255) << 16 - x2 = (a2 & 255) << 8 - x3 = x1 | x2 | a3 - return self.character[(x3 & 16515072) >> 18] + self.character[(x3 & 258048) >> 12] + self.character[(x3 & 4032) >> 6] + self.character[ - x3 & 63] - - - def getXBogus(self, url_path): - """ - 获取 X-Bogus 值。 - Get the X-Bogus value. - """ - array1 = self.md5_str_to_array("d88201c9344707acde7261b158656c0e") - array2 = self.md5_str_to_array( - self.md5(self.md5_str_to_array("d41d8cd98f00b204e9800998ecf8427e"))) - url_path_array = self.md5_encrypt(url_path) - - timer = int(time.time()) - ct = 536919696 - array3 = [] - array4 = [] - xb_ = "" - - new_array = [ - 64, 0.00390625, 1, 8, - url_path_array[14], url_path_array[15], array2[14], array2[15], array1[14], array1[15], - timer >> 24 & 255, timer >> 16 & 255, timer >> 8 & 255, timer & 255, - ct >> 24 & 255, ct >> 16 & 255, ct >> 8 & 255, ct & 255 - ] - - xor_result = new_array[0] - for i in range(1, len(new_array)): - # a = xor_result - b = new_array[i] - if isinstance(b, float): - b = int(b) - xor_result ^= b - - new_array.append(xor_result) - - idx = 0 - while idx < len(new_array): - array3.append(new_array[idx]) - try: - array4.append(new_array[idx + 1]) - except IndexError: - pass - idx += 2 - - merge_array = array3 + array4 - - garbled_code = self.encoding_conversion2( - 2, 255, self.rc4_encrypt("ÿ".encode('ISO-8859-1'), self.encoding_conversion(*merge_array).encode('ISO-8859-1')).decode('ISO-8859-1')) - - idx = 0 - while idx < len(garbled_code): - xb_ += self.calculation(ord(garbled_code[idx]), ord( - garbled_code[idx + 1]), ord(garbled_code[idx + 2])) - idx += 3 - self.params = '%s&X-Bogus=%s' % (url_path, xb_) - self.xb = xb_ - return (self.params, self.xb) - - -if __name__ == '__main__': - url_path = "aweme_id=7196239141472980280&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333" - print("URL_path:", url_path) - XB = XBogus() - xbogus = XB.getXBogus(url_path) - print("XB_ogus:", xbogus[1]) \ No newline at end of file diff --git a/Util/__init__.py b/Util/__init__.py deleted file mode 100644 index 1b6ea7a0..00000000 --- a/Util/__init__.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:__init__.py -@Date :2022/07/29 23:20:56 -@Author :JohnserfSeed -@version :1.4.2.2 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserfseed@gmail.com -------------------------------------------------- -Change Log : -2022/07/29 23:20:56 : Init -2022/08/16 18:34:27 : Add moudle Log -2023/03/10 15:27:18 : Add rich download progress -------------------------------------------------- -''' - -# 标准库 -import re -import io -import os -import sys -import json -import time -import math -import signal -import random -import asyncio -import logging -import platform -import argparse -import base64 -import traceback -from urllib import parse -from urllib.request import urlopen -from urllib.parse import urlparse -from functools import partial -from typing import Union, Optional -from concurrent.futures import ThreadPoolExecutor -from pathlib import Path - -# 第三方库 -import aiohttp -import requests - -from PIL import Image -from lxml import etree -import rich -import qrcode -from configobj import ConfigObj -from rich.progress import ( - BarColumn, - DownloadColumn, - Progress, - TaskID, - TextColumn, - TimeRemainingColumn, - TransferSpeedColumn, -) -from rich.console import Console -from rich.table import Table -from rich.panel import Panel - -# 自定义模块 -from TikTokUpdata import Updata -from .XB import XBogus -from .Log import Log -from .Urls import Urls -from .Lives import Lives -from .Login import * -from .Check import Check -from .Config import Config -from .Command import Command -from .Cookies import Cookies -from .Profile import Profile -from .Download import Download -from .NickMapper import NickMapper -from . import __version__ - - -# 日志记录 -log = Log() - -def replaceT(obj): - """ - 替换文案非法字符 - Args: - obj (_type_): 传入对象 - Returns: - new: 处理后的内容 - """ - if len(obj) > 100: - obj = obj[:100] - reSub = r"[^\u4e00-\u9fa5^a-z^A-Z^0-9^#]" - new = [] - if type(obj) == list: - for i in obj: - # 替换为下划线 - retest = re.sub(reSub, "_", i) - new.append(retest) - elif type(obj) == str: - # 替换为下划线 - new = re.sub(reSub, "_", obj, 0, re.MULTILINE) - return new - - -def reFind(strurl: str) -> str: - """ - 匹配分享的url地址 - Args: - strurl (str): 带文案的分享链接 - Returns: - str: 验证url是否有效,如果不是url类型则返回空 - """ - - # 确保输入是字符串 - if not isinstance(strurl, str): - return '' - - results = re.findall( - 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', strurl) - - if not results: - return '' - - # 验证url - for url in results: - try: - parts = urlparse(url) - if parts.scheme in ['http', 'https'] and parts.netloc != '': - return url - except ValueError: - return '' - - -table = Table.grid(padding=1, pad_edge=True) -table.add_column(no_wrap=True, justify="left") -table.add_row(__version__.__help__) - - -console = Console() -console = rich.console.Console(color_system="truecolor") -console.print(f"{__version__.__logo__}", justify="center") -console.print(f"\n:rocket: [bold]TikTokDownload [bright_yellow]{__version__.__version__}[/bright_yellow] :rocket:", justify="center") -console.print(f":zap: [i]{__version__.__description_cn__} :zap:", justify="center") -console.print(f":fire: [i]{__version__.__description_en__} :fire:", justify="center") -console.print(f":computer: [i]Repo {__version__.__repourl__} :computer:\n", justify="center") -console.print(Panel(table, border_style="bold", title="使用说明")) - - -progress = Progress( - TextColumn("{task.description}[bold blue]{task.fields[filename]}", justify="left"), - BarColumn(bar_width=30), - "[progress.percentage]{task.percentage:>3.1f}%", - "•", - DownloadColumn(), - "•", - TransferSpeedColumn(), - "•", - TimeRemainingColumn(), - console=console, - expand=True -) - -done_event = asyncio.Event() - -bound_handle_sigint = lambda signum, frame: handle_sigint() - -signal.signal(signal.SIGINT, bound_handle_sigint) - -# 设置中断信号 -def handle_sigint(): - done_event.set() - - -if (platform.system() == 'Windows'): - # 💻 - console.print('[ 💻 ]:Windows平台') -elif (platform.system() == 'Linux'): - # 🐧 - console.print('[ 🐧 ]:Linux平台') -else: - # 🍎 - console.print('[ 🍎 ]:MacOS平台') - - -# 输出操作系统版本 -log.info(platform.system()) diff --git a/Util/__version__.py b/Util/__version__.py deleted file mode 100644 index 60c6626f..00000000 --- a/Util/__version__.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:__version__.py -@Date :2023/08/04 15:35:24 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/08/04 15:35:24 - Create __version__.py -------------------------------------------------- -''' - -__author__ = 'JohnserfSeed' -__description_cn__ = '基于[red]异步[/red]的抖音[bright_magenta]作品下载工具' -__description_en__ = 'An [yellow]asynchronous [/yellow]TikTok [dark_cyan]video downloader tool' -__repo__ = 'TikTokDownload' -__repourl__ = '[bright_white]https://github.com/Johnserf-Seed/TikTokDownload[/bright_white]' -__version__ = '1.4.2.2' - -__help__ = """ -1. 程序支持命令行调用和配置文件操作,WebUI即将发布 -2. 命令行操作方法: - - 将本程序路径添加到环境变量 - - 控制台输入 [dark_cyan][i]TikTokTool -u [bright_blue]https://v.douyin.com/iJLVC5xq/[/bright_blue][/i][/dark_cyan] - -3. 配置文件操作方法: - - 运行软件前先打开目录下 [dark_cyan]conf.ini[/dark_cyan] 文件配置用户主页和下载模式 - - 按照控制台输出信息操作 - -4. 获取更多命令行帮助请输入 [dark_cyan][i]TikTokTool -h[/i][/dark_cyan],支持app内分享短链和web端长链 - -5. 新版本支持扫码登录,务必妥善保存你的[yellow]Cookie[/yellow]信息,issues贴log文件的时候注意脱敏,[red]防止泄露[/red] -6. 有任何bug或者需求建议请按模板发起 [bright_blue][i]https://github.com/Johnserf-Seed/TikTokDownload/issues[/i][/bright_blue] -7. 打包的版本发布在 [bright_blue][i]https://github.com/Johnserf-Seed/TikTokDownload/tags[/i][/bright_blue] -8. TikTokLive 输入抖音直播间web端链接,例如 [bright_blue][i]https://live.douyin.com/176819813905[/i][/bright_blue] -9. 新版工具fastdl正在开发中 ----> [bright_blue][i]https://github.com/Johnserf-Seed/fastdl[/i][/bright_blue] - -[bright_yellow][b]注意: 目前已经更新用户昵称映射,改昵称不会重新下载作品[/b][/bright_yellow] - """ - -__logo__ = ''' -████████╗██╗██╗ ██╗████████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ██╗ ██╗███╗ ██╗██╗ ██████╗ █████╗ ██████╗ -╚══██╔══╝██║██║ ██╔╝╚══██╔══╝██╔═══██╗██║ ██╔╝██╔══██╗██╔═══██╗██║ ██║████╗ ██║██║ ██╔═══██╗██╔══██╗██╔══██╗ - ██║ ██║█████╔╝ ██║ ██║ ██║█████╔╝ ██║ ██║██║ ██║██║ █╗ ██║██╔██╗ ██║██║ ██║ ██║███████║██║ ██║ - ██║ ██║██╔═██╗ ██║ ██║ ██║██╔═██╗ ██║ ██║██║ ██║██║███╗██║██║╚██╗██║██║ ██║ ██║██╔══██║██║ ██║ - ██║ ██║██║ ██╗ ██║ ╚██████╔╝██║ ██╗██████╔╝╚██████╔╝╚███╔███╔╝██║ ╚████║███████╗╚██████╔╝██║ ██║██████╔╝ - ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ - ''' \ No newline at end of file diff --git a/Util/algorithm/Server.py b/Util/algorithm/Server.py deleted file mode 100644 index 63270dda..00000000 --- a/Util/algorithm/Server.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:Server.py -@Date :2023/02/25 17:03:32 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/02/25 17:03:32 - Create Flask Server XB Gen -2023/08/03 16:48:34 - Fix ttwid -------------------------------------------------- -''' - -import time -import execjs -# import sqlite3 -import requests - -from flask import Flask -from flask import request -from flask import jsonify -# from flask import make_response -# from flask import render_template - -from urllib.parse import urlencode -from urllib.parse import unquote -from urllib.parse import parse_qsl - -class Server: - def __init__(self) -> None: - # 工厂模式 - self.app = Flask(__name__) - - self.app.config.from_mapping( - SECRET_KEY='douyin-xbogus' - ) - - self.app.config['JSON_AS_ASCII'] = False - - with open("x-bogus.js", "r", encoding="utf-8") as fp: - self.xbogust_func = execjs.compile(fp.read()) - - with open("x-tt-params.js", "r", encoding="utf-8") as fp: - self.xttm_func = execjs.compile(fp.read()) - - self.ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" - - # 获取xg参数 - def getXG(self, url_path, params): - xbogus = self.xbogust_func.call("getXB", url_path) - # 字典中添加xg - params["X-Bogus"] = xbogus - tips = { - "status_code": "200", - "time": { - "strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "timestamp": int(round(time.time() * 1000)) - }, - "result": [{ - "params": params, - "paramsencode": urlencode(params, safe="="), - "user-agent": self.ua, - "X-Bogus": { - 0: xbogus, - 1: "X-Bogus=%s" % xbogus - } - }] - } - print(tips) - return jsonify(tips) - - # 生成x-tt-params - def getxttparams(self, url_path): - xttp = self.xttm_func.call("getXTTP", url_path) - tips = { - "status_code": "200", - "time": { - "strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "timestamp": int(round(time.time() * 1000)) - }, - "result": [{ - "headers": { - "user-agent": self.ua, - "x-tt-params": xttp - } - }] - } - print(tips) - return jsonify(tips) - - def gen_ttwid(self) -> str: - """生成请求必带的ttwid - param :None - return:ttwid - """ - url = 'https://ttwid.bytedance.com/ttwid/union/register/' - data = '{"region":"cn","aid":1768,"needFid":false,"service":"www.ixigua.com","migrate_info":{"ticket":"","source":"node"},"cbUrlProtocol":"https","union":true}' - response = requests.request("POST", url, data=data) - # j = ttwid k = 1%7CfPx9ZM..... - for j, k in response.cookies.items(): - tips = { - "status_code": "200", - "time": { - "strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "timestamp": int(round(time.time() * 1000)) - }, - "result": [{ - "headers": { - "user-agent": self.ua, - "cookie": "ttwid=%s;" % k - } - }] - } - print(tips) - return jsonify(tips) - - -if __name__ == "__main__": - server = Server() - # 首页 - @server.app.route('/', methods=['GET', 'POST']) - def index(): - tips = { - "status_code": "-1", - "time": { - "strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "timestamp": int(round(time.time() * 1000)) - }, - "path": { - 0: "/xg/path/?url=", - 2: "/x-tt-params/path" - } - } - print(tips) - return jsonify(tips) - - # xg参数 - @server.app.route('/xg/path/', methods=['GET', 'POST']) - def xgpath(): - path = request.args.get('url', '') - # 如果str路径为空 - if not path: - tips = { - "status_code": "-3", - "time": { - "strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "timestamp": int(round(time.time() * 1000)) - }, - "message": { - 0: "The key url cannot be empty and the need for url encoding, The '&' sign needs to be escaped to '%26', Use urllib.parse.quote(url) to escape. Example:/xg/path/?url=aid=6383%26sec_user_id=xxx%26max_cursor=0%26count=10", - 1: "url参数不能为空,且需要注意传入值中的“&”需要转义成“%26”,使用urllib.parse.quote(url)转义. 例如:/xg/path/?url=aid=6383%26sec_user_id=xxx%26max_cursor=0%26count=10" - } - } - print(tips) - return jsonify(tips) - else: - # url转字典 - params = dict(parse_qsl(path)) - # 字典转url - url_path = urlencode(params, safe="=") - return server.getXG(url_path, params) - - # x-tt-params参数 - @server.app.route('/x-tt-params/path', methods=['GET', 'POST']) - def xttppath(): - try: - path = request.json - except: - pass - if not path: - tips = { - "status_code": "-5", - "time": { - "strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "timestamp": int(round(time.time() * 1000)) - }, - "message": { - 0: "Body uses raw JSON format to pass dictionary parameters, such as %s" % '{"aid": 1988,"app_name": "tiktok_web","channel": "tiktok_web".........}', - 1: "body中使用raw json格式传递字典参数,如%s" % '{"aid": 1988,"app_name": "tiktok_web","channel": "tiktok_web".........}' - } - } - print(tips) - return jsonify(tips) - else: - return server.getxttparams(path) - - # ttwid - @server.app.route('/xg/ttwid', methods=['GET', 'POST']) - def ttwid(): - return server.gen_ttwid() - - - server.app.run(host='0.0.0.0',port='8889') diff --git a/Util/algorithm/package.json b/Util/algorithm/package.json deleted file mode 100644 index 6f318077..00000000 --- a/Util/algorithm/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "dependencies": { - "crypto-js": "^4.1.1", - "md5": "^2.3.0" - } -} diff --git a/Util/algorithm/s_v_web_id.js b/Util/algorithm/s_v_web_id.js deleted file mode 100644 index 6d620aca..00000000 --- a/Util/algorithm/s_v_web_id.js +++ /dev/null @@ -1,15 +0,0 @@ -function create_s_v_web_id() { - var e = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("") - , t = e.length - , n = (new Date).getTime().toString(36) - , r = []; - - r[8] = r[13] = r[18] = r[23] = "_", - r[14] = "4"; - for (var o, i = 0; i < 36; i++) - r[i] || (o = 0 | Math.random() * t, - r[i] = e[19 == i ? 3 & o | 8 : o]); - return "verify_" + n + "_" + r.join("") -} - -console.log(create_s_v_web_id()) \ No newline at end of file diff --git a/Util/algorithm/s_v_web_id.py b/Util/algorithm/s_v_web_id.py deleted file mode 100644 index c93d99f5..00000000 --- a/Util/algorithm/s_v_web_id.py +++ /dev/null @@ -1,31 +0,0 @@ -import time -import random - -def create_s_v_web_id(): - e = list("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") - t = len(e) - n = base36_encode(int(time.time()*1000)) # Convert timestamp to base 36 - - r = [''] * 36 - r[8] = r[13] = r[18] = r[23] = "_" - r[14] = "4" - - for i in range(36): - if not r[i]: - o = int(random.random() * t) - r[i] = e[3 & o | 8 if i == 19 else o] - - return "verify_" + n + "_" + "".join(r) - -def base36_encode(number): - """Converts an integer to a base36 string.""" - alphabet = '0123456789abcdefghijklmnopqrstuvwxyz' - base36 = [] - - while number: - number, i = divmod(number, 36) - base36.append(alphabet[i]) - - return ''.join(reversed(base36)) - -print(create_s_v_web_id()) \ No newline at end of file diff --git a/Util/algorithm/x-bogus.js b/Util/algorithm/x-bogus.js deleted file mode 100644 index 6e73604a..00000000 --- a/Util/algorithm/x-bogus.js +++ /dev/null @@ -1,132 +0,0 @@ -let MD5 = require("md5"); - -let Array = [ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 10, 11, 12, 13, 14, 15 ]; - -// let _0x4129ad = 'Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe'; -// let _0x127ecb = '='; -let _0x377d66 = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe="; - -function _0x39ced2(l) { - let n = []; - for (let u = 0; u < l.length; ) { - n.push(Array[l.charCodeAt(u++)] << 4 | Array[l.charCodeAt(u++)]); - } - return n; -} - -function _0x1da120(l) { - return _0x39ced2(MD5(_0x39ced2(MD5(l)))); -} - -function _0x2efd11(l) { - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(l); -} - -function _0x2d9dba(l) { - var n, u, e, t, r, o = ""; - for (n = 0; n < l.length - 3; n += 4) { - u = _0x2efd11(l.charAt(n)), e = _0x2efd11(l.charAt(n + 1)), t = _0x2efd11(l.charAt(n + 2)), - r = _0x2efd11(l.charAt(n + 3)), o += String.fromCharCode(u << 2 | e >>> 4), "=" !== l.charAt(n + 2) && (o += String.fromCharCode(e << 4 & 240 | t >>> 2 & 15)), - "=" !== l.charAt(n + 3) && (o += String.fromCharCode(t << 6 & 192 | r)); - } - return o; -} - -function _0x24e7c9() { - var l = ""; - try { - window.sessionStorage && (l = window.sessionStorage.getItem("_byted_param_sw")), - l && !window.localStorage || (l = window.localStorage.getItem("_byted_param_sw")); - } catch (l) {} - if (l) { - try { - var n = _0x3459bb(_0x2d9dba(l.slice(8)), l.slice(0, 8)); - if ("on" === n) { - return !0; - } - if ("off" === n) { - return !1; - } - } catch (l) {} - } - return !1; -} - -function _0x4d54ed(l) { - try { - return window.localStorage ? window.localStorage.getItem(l) : null; - } catch (l) { - return null; - } -} - -function _0x478bb3(l, n, u) { - let e = (255 & l) << 16; - let t = (255 & n) << 8; - let r = e | t | u; - return _0x377d66[(16515072 & r) >> 18] + _0x377d66[(258048 & r) >> 12] + _0x377d66[(4032 & r) >> 6] + _0x377d66[63 & r]; -} - -function _0x481826(l) { - void 0 !== l && "" != l && (_0x402a35.ttwid = l); -} - -function _0x37f15d() { - var l = _0x4d54ed("xmst"); - return l || ""; -} - -function _0x330d11(l, n, u, e, t, r, o, d, a, c, i, f, x, _, h, g, C, s, p) { - let w = new Uint8Array(19); - w[0] = l, w[1] = i, w[2] = n, w[3] = f, w[4] = u, w[5] = x, w[6] = e, w[7] = _, - w[8] = t, w[9] = h, w[10] = r, w[11] = g, w[12] = o, w[13] = C, w[14] = d, w[15] = s, - w[16] = a, w[17] = p, w[18] = c; - return String.fromCharCode.apply(null, w); -} - -function _0x330d112(l, n) { - let u, e = [], t = 0, r = "", o = 0, d = 0, a = 0; - for (let l = 0; l < 256; l++) { - e[l] = l; - } - for (;o < 256; o++) { - t = (t + e[o] + l.charCodeAt(o % l.length)) % 256, u = e[o], e[o] = e[t], e[t] = u; - } - t = 0; - for (;d < n.length; d++) { - t = (t + e[a = (a + 1) % 256]) % 256, u = e[a], e[a] = e[t], e[t] = u, r += String.fromCharCode(n.charCodeAt(d) ^ e[(e[a] + e[t]) % 256]); - } - return r; -} - -function _0x33baa6(l, n, u) { - return String.fromCharCode(l) + String.fromCharCode(n) + u; -} - -function getXB(l) { - // douyin - let n = _0x39ced2(MD5("d4+pTKoNjJFb5tMtAC3XB9XrDDxlig1kjbh32u+x5YcwWb/me2pvLTh6ZdBVN5skEeIaOYNixbnFK6wyJdl/Lcy9CDAcpXLLQc3QFKIDQ3KkQYie3n258eLS1YFUqFLDjn7dqCRp1jjoORamU2SV")); - // douyin & tiktok - let u = _0x39ced2(MD5(_0x39ced2("d41d8cd98f00b204e9800998ecf8427e"))); - let e = _0x1da120(l), t = new Date().getTime() / 1e3, r = 536919696, o = [], d = [], a = ""; - let c = [ 64, .00390625, 1, 8, e[14], e[15], u[14], u[15], n[14], n[15], t >> 24 & 255, t >> 16 & 255, t >> 8 & 255, t >> 0 & 255, r >> 24 & 255, r >> 16 & 255, r >> 8 & 255, r >> 0 & 255 ]; - c.push(c.reduce(function(l, n) { - return l ^ n; - })); - for (let l = 0; l < c.length; l += 2) { - o.push(c[l]); - d.push(c[l + 1]); - } - //unescape('%FF') - let i = _0x33baa6.apply(null, [ 2, 255, _0x330d112.apply(null, [String.fromCharCode(255), _0x330d11.apply(null, o.concat(d).slice(0, 19)) ]) ]); - for (let l = 0; l < i.length; ) { - a += _0x478bb3(i.charCodeAt(l++), i.charCodeAt(l++), i.charCodeAt(l++)); - } - return a; -} - -_0x180b4c = _0x37f15d(); - -module.exports = { - getXB: getXB -}; \ No newline at end of file diff --git a/Util/algorithm/x-tt-params.js b/Util/algorithm/x-tt-params.js deleted file mode 100644 index 8522576f..00000000 --- a/Util/algorithm/x-tt-params.js +++ /dev/null @@ -1,27 +0,0 @@ -let CryptoJS = require("crypto-js"); -getXTTP = e => { - const t = []; - return Object.keys(e).forEach((i => { - const o = `${i}=${e[i]}`; - t.push(o) - })), - t.push("is_encryption=1"), - ((e, t) => { - const i = ((e, t) => { - let i = e.toString(); - const o = i.length; - return o < 16 ? i = new Array(16 - o + 1).join("0") + i : o > 16 && (i = i.slice(0, 16)), - i - })("webapp1.0+20210628"), - n = CryptoJS.enc.Utf8.parse(i); - return CryptoJS.AES.encrypt(e, n, { - iv: n, - mode: CryptoJS.mode.CBC, - padding: CryptoJS.pad.Pkcs7 - }).toString() - })(t.join("&")) -} - -module.exports = { - getXTTP: getXTTP -}; \ No newline at end of file diff --git a/build-win.bat b/build-win.bat index c2cb15ed..9d684c50 100644 --- a/build-win.bat +++ b/build-win.bat @@ -4,11 +4,7 @@ pip install -r requirements.txt echo Build EXE version, Press Ctrl + C to Exit pause echo Build Server -pyinstaller -F -i logo.ico --distpath Util --version-file API\Server.txt --hidden-import=charset_normalizer.md__mypyc Util\algorithm\Server.py +pyinstaller -F -i f2-logo.ico --version-file API\Server.txt --hidden-import=charset_normalizer.md__mypyc Server\Server.py echo Bulid TikTokTool -pyinstaller -F -i logo.ico --version-file API\TikTokTool.txt --hidden-import=charset_normalizer.md__mypyc TikTokTool.py -echo Bulid TikTokLive -pyinstaller -F -i logo.ico --version-file API\TikTokLive.txt --hidden-import=charset_normalizer.md__mypyc TikTokLive.py -echo Bulid TikTokUpdata -pyinstaller -F -i logo.ico --version-file API\TikTokUpdata.txt --hidden-import=charset_normalizer.md__mypyc TikTokUpdata.py +pyinstaller -F -i f2-logo.ico --version-file API\TikTokTool.txt --hidden-import=charset_normalizer.md__mypyc TikTokTool.py pause \ No newline at end of file diff --git a/conf.ini b/conf.ini deleted file mode 100644 index e2ad4093..00000000 --- a/conf.ini +++ /dev/null @@ -1,53 +0,0 @@ -# 用户主页(非视频链接) -# 单视频请用TikTokDownload或TikTokWeb -uid = https://www.douyin.com/user/MS4wLjABAAAAB7geBosrrW1XPPqgd88hbbKF8fymijEvW5wTsq4qIK6mDkbb5Ycvl8_fxDrHCawy?vid=7259985959591218484 - -# 视频原声保存(yes|no) -music = yes - -# 视频封面保存(yes|no) -cover = yes - -# 视频文案保存(yes|no) -desc = yes - -# 作品保存位置,只支持相对路径 -# 不了解不用修改 -path = Download - -# 作品保存到单独的文件夹(yes|no) -# 如果设置了保存原声、封面、文案的建议开启该选项 -folderize = yes - -# 下载模式(post|like|listcollection|wix合集暂未更新) -# 下载他人喜欢页、收藏视频请确保是开放所有人可见 -# 如果是扫码登陆的下载自己的喜欢与收藏则无需设置所有人可见 -mode = post - -# 全局作品文件命名 -# {create}发布时间、{desc}作品文案、{id}作品id -# 只允许下划线_ 减号- 作为文件名间隔符 -# 该配置会影响folderize中的作品文件夹命名 -naming = {create}_{desc} - -# 作品、收藏、历史、好友、推荐等需要登录后的cookie -# 置空程序会自动扫码获取cookie -cookie = - -# 根据作品发布日期区间下载作品 -# 例如2022-01-01|2023-01-01下载的是2022年所有作品 -# 填all即是下载全部时间 -interval = all - -# 自动更新(yes|no) -# 由于更新频率快,默认yes可以保持最新版本 -# 关闭后仅提示有新版可下载 -update = yes - -# 网络请求并发连接数 -# 不宜设置过大,如遇错误可适当降低 -max_connections = 5 - -# 异步的任务数 -# 不宜设置过大,如遇错误可适当降低 -max_tasks = 10 \ No newline at end of file diff --git a/example.py b/example.py deleted file mode 100644 index 541a27e6..00000000 --- a/example.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@Description:example.py -@Date :2023/08/04 01:35:15 -@Author :JohnserfSeed -@version :0.0.1 -@License :MIT License -@Github :https://github.com/johnserf-seed -@Mail :johnserf-seed@foxmail.com -------------------------------------------------- -Change Log : -2023/08/04 01:35:15 - 生成测试用例 -------------------------------------------------- -''' - -import Util.NickMapper as NickMapper -import Util - -def example_NickMapper(): - mapper = NickMapper('nickname_mapping.db') - mapper.connect() - - # 添加昵称映射 - mapper.add_mapping('123456', 'UserA') - mapper.add_mapping('789012', 'UserB') - - # 更新昵称映射 - mapper.update_mapping('123456', 'NewUserA') - - # 获取昵称映射 - nickname = mapper.get_nickname('123456') - Util.console.print(nickname) # 输出: NewUserA - - # 删除昵称映射 - mapper.delete_mapping('789012') - - mapper.close() - - -def exception_handling_example(): - mapper = NickMapper('nickname_mapping.db') - mapper.connect() - - try: - # 更新一个不存在的映射 - mapper.update_mapping('non_existent_id', 'SomeNickname') - except ValueError as e: - Util.console.print(f'Caught an exception: {str(e)}') - - # 尝试添加一个已经存在的映射 - mapper.add_mapping('123456', 'UserA') # 已经存在 - Util.console.print(mapper.get_nickname('123456')) # 应该输出 UserA,因为映射不应该被修改 - - # 尝试获取一个不存在的映射 - Util.console.print(mapper.get_nickname('non_existent_id')) # 应该输出 None - - # 尝试删除一个不存在的映射 - mapper.delete_mapping('non_existent_id') # 应该不会抛出任何异常 - - mapper.close() - - -# 异常处理示例 -exception_handling_example() - - - -# async def main(): -# # 测试 re_match 方法 -# async with Util.aiohttp.ClientSession() as session: -# match = await profile.re_match(session, 'https://v.douyin.com/iJ8WnuY3/') -# Util.console.print(f"re_match 方法返回的结果: {match.group()}") -# # 测试 get_all_sec_user_id 方法 -# sec_user_id = await profile.get_all_sec_user_id('https://v.douyin.com/iJ8WnuY3/') -# Util.console.print(f"get_all_sec_user_id 方法返回的结果: {sec_user_id}") -# # 测试 get_diff_type_url 方法 -# url = await profile.get_diff_type_url(config, sec_user_id, 35, 0) -# Util.console.print(f"get_diff_type_url 方法返回的结果: {url}") -# # 测试 get_user_base_info 方法 -# user_info = await profile.get_user_base_info(dyheaders, sec_user_id) -# Util.console.print(f"get_user_base_info 方法返回的结果: {user_info}") - -# # 测试 get_user_profile_info 方法 -# profile_info = await profile.get_user_profile_info(dyheaders, sec_user_id) -# Util.console.print(f"get_user_profile_info 方法返回的结果: {profile_info}") - -# # 测试 get_request_data 方法 -# if config['mode'] == 'listcollection': -# dyheaders['Content-Type'] = 'application/x-www-form-urlencoded' -# aweme_list, max_cursor, has_more = await profile.get_request_data('POST', url, dyheaders , 'count=20&cursor=0') -# else: -# aweme_list, max_cursor, has_more = await profile.get_request_data('GET', url, dyheaders) -# Util.console.print(f"get_request_data 方法返回的结果: {aweme_list}, {max_cursor}, {has_more}") - - - -if __name__ == '__main__': - # # 获取命令行和配置文件 - # cmd = Util.Command() - # config = cmd.config_dict - # dyheaders = cmd.dyheaders - - # # 创建 Profile 类实例 - # profile = Util.Profile(config, dyheaders) - - # # 运行测试代码 - # Util.asyncio.run(main()) - - # # 昵称映射表 - # example_NickMapper() - - # 昵称映射表异常处理示例 - exception_handling_example() \ No newline at end of file diff --git a/f2-logo.ico b/f2-logo.ico new file mode 100644 index 00000000..453da513 Binary files /dev/null and b/f2-logo.ico differ diff --git a/info.db b/info.db deleted file mode 100644 index 6c21a3db..00000000 Binary files a/info.db and /dev/null differ diff --git a/requirements.txt b/requirements.txt index 365304c8..70f80fc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1 @@ -aiohttp==3.8.5 -configobj==5.0.8 -Flask==2.2.5 -gradio==3.39.0 -lxml==4.9.2 -Pillow==10.0.0 -PyExecJS==1.5.1 -qrcode==7.4.2 -requests==2.28.2 -retrying==1.3.3 -rich==13.5.2 -setuptools==65.5.1 +f2 \ No newline at end of file diff --git a/version b/version index 6335c61d..f0cc45cd 100644 --- a/version +++ b/version @@ -1 +1 @@ -14200 \ No newline at end of file +15000 \ No newline at end of file