[
  {
    "path": ".babelrc",
    "content": "{\n    \"presets\": [\n        [\"env\", {\n            \"targets\": {\n                \"browsers\": [\"last 1 Chrome version\"]\n            }\n        }]\n    ],\n    \"plugins\": [\n        [\"transform-runtime\",\n        {\n            \"polyfill\": false,\n            \"regenerator\": true\n        }]\n    ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"cSpell.words\": [\n    \"Lazyload\",\n    \"fliggy\",\n    \"metatit\",\n    \"tmall\"\n  ]\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"teaclub\",\n  \"version\": \"0.0.1\",\n  \"author\": \"Ming\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"npx webpack --config webpack.config.js\",\n    \"build\": \"NODE_ENV=production yarn start\",\n    \"dev\": \"webpack --watch\"\n  },\n  \"dependencies\": {\n    \"@sunoj/touchemulator\": \"^0.0.1\",\n    \"babel-plugin-transform-runtime\": \"^6.23.0\",\n    \"dexie\": \"^2.0.4\",\n    \"gulp\": \"^4.0.0\",\n    \"gulp-clean-css\": \"^3.9.0\",\n    \"gulp-concat\": \"^2.6.1\",\n    \"gulp-preprocess\": \"^2.0.0\",\n    \"gulp-replace\": \"^0.6.1\",\n    \"gulp-watch\": \"^4.3.11\",\n    \"hooper\": \"^0.1.5\",\n    \"jobs\": \"^0.0.4\",\n    \"jquery\": \"3.5\",\n    \"lodash\": \"^4.17.19\",\n    \"logline\": \"^1.1.2\",\n    \"luxon\": \"^1.4.3\",\n    \"microtip\": \"^0.2.2\",\n    \"parcel-bundler\": \"^1.12.3\",\n    \"qrcode-svg\": \"^1.1.0\",\n    \"vue\": \"^2.6.11\",\n    \"vue-infinite-loading\": \"^2.4.5\",\n    \"vue-lazyload\": \"^1.3.3\",\n    \"weui\": \"^2.3.0\",\n    \"weui.js\": \"^1.2.1\",\n    \"zepto\": \"^1.2.0\"\n  },\n  \"alias\": {\n    \"vue\": \"./node_modules/vue/dist/vue.common.js\"\n  },\n  \"devDependencies\": {\n    \"@vue/component-compiler-utils\": \"^2.6.0\",\n    \"babel-core\": \"^6.26.3\",\n    \"babel-preset-env\": \"^1.7.0\",\n    \"clean-webpack-plugin\": \"^3.0.0\",\n    \"copy-webpack-plugin\": \"^5.1.1\",\n    \"css-loader\": \"^3.5.3\",\n    \"file-loader\": \"^6.0.0\",\n    \"html-webpack-plugin\": \"^4.3.0\",\n    \"less\": \"^3.11.1\",\n    \"less-loader\": \"^6.0.0\",\n    \"parcel-plugin-static-files-copy\": \"^2.3.1\",\n    \"style-loader\": \"^1.2.1\",\n    \"svg-inline-loader\": \"^0.8.2\",\n    \"svg-url-loader\": \"^5.0.0\",\n    \"url-loader\": \"^4.1.0\",\n    \"vue-loader\": \"^15.9.1\",\n    \"vue-template-compiler\": \"^2.6.11\",\n    \"webpack\": \"^4.43.0\",\n    \"webpack-cli\": \"^3.3.11\"\n  }\n}\n"
  },
  {
    "path": "public/background.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <script src=\"static/background.js\"></script>\n  </head>\n  <body>\n    <iframe id=\"iframe\" width=\"400 px\" height=\"600 px\"></iframe>\n  </body>\n</html>"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"manifest_version\": 2,\n  \"name\": \"茶友会 - 淘宝查券助手\",\n  \"short_name\": \"茶友会\",\n  \"description\": \"茶友会是自动为你查找淘宝优惠券，自动签到领飞猪里程的多功能购物助手\",\n  \"version\": \"0.3.1\",\n  \"background\": {\n    \"page\": \"background.html\",\n    \"persistent\": true\n  },\n  \"browser_action\": {\n    \"default_icon\": \"static/image/icon.png\",\n    \"default_popup\": \"popup.html\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"*://*.taobao.com/*\", \"*://*.tmall.com/*\", \"*://*.fliggy.com/*\"],\n      \"exclude_matches\": [\"*://ratewrite.tmall.com/*\", \"*://rate.taobao.com/*\", \"*://passport.taobao.com/*\", \"*://buy.taobao.com/*\", \"*://buy.tmall.com/*\"],\n      \"js\": [\n        \"static/zepto.min.js\",\n        \"static/content_script.js\"\n      ],\n      \"run_at\": \"document_end\",\n      \"all_frames\": true\n    }\n  ],\n  \"content_security_policy\": \"script-src 'self' 'unsafe-eval'; object-src 'self';\",\n  \"icons\": {\n    \"16\": \"static/image/icon@16.png\",\n    \"48\": \"static/image/icon@48.png\",\n    \"128\": \"static/image/icon@128.png\"\n  },\n  \"web_accessible_resources\": [\n    \"static/touch-emulator.js\"\n  ],\n  \"permissions\": [\n    \"*://*.taobao.com/*\",\n    \"*://*.tmall.com/*\",\n    \"*://*.fliggy.com/*\",\n    \"webRequest\",\n    \"webRequestBlocking\",\n    \"alarms\",\n    \"contextMenus\",\n    \"notifications\"\n  ]\n}"
  },
  {
    "path": "public/popup.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>茶友会</title>\n  <meta name=\"viewport\" content=\"user-scalable=no\" />\n</head>\n\n<body data-weui-theme=\"light\">\n  <div class=\"popup\" id=\"popup\">\n    <div id=\"app\"></div>\n  </div>\n  <!-- body 最后 -->\n  <script src=\"static/popup.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "public/start.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>茶友会安装成功！</title>\n  </head>\n  <body>\n    <div class=\"start\">\n      <div class=\"page msg_success js_show\">\n        <div class=\"weui-msg\">\n          <div class=\"weui-msg__icon-area\"><i class=\"weui-icon-success weui-icon_msg\"></i></div>\n          <div class=\"weui-msg__text-area\">\n            <h2 class=\"weui-msg__title\">茶友会安装成功</h2>\n            <p class=\"weui-msg__desc\">我可以自动为你自动签到领飞猪里程、自动查找渠道优惠券</p>\n            <img class=\"find-coupon\" src=\"https://jjbcdn.zaoshu.so/teaclub/find-coupon.gif\" alt=\"自动找券演示\">\n          </div>\n          <div class=\"weui-msg__opr-area\">\n            <p class=\"weui-btn-area\">\n                <a href=\"https://s.click.taobao.com/t?e=m%3D2%26s%3D%2B9llzsJIcM0cQipKwQzePCperVdZeJvipRe%2F8jaAHci5VBFTL4hn2dDrVEnLBoSlc4zWPc6e822Db670SJ30CbMrQT2ouyP2AXDMm4A2j9lz%2B247WylknIVK987Q3rqfM7kxpdONUAI%2BTx4Gw%2F2vfyT8IooNW8SzlmpSTfxBwAxgkfYt2XlEMH96zNtHoLpw2dsnFJC0C7enlwybGdCkReewTZ7SVcdIGejP7SXXNsoWP2mcc3LMu5j%2B7kFbwj1JvDj80XT4gyoWV9M8fA%2BI3AZR9GdqBzz5P8z7%2F1pu85%2FWMe%2FYdh9vkNlnivLx%2B4MuRicMup9U2qVxKmPmpIKZsA%3D%3D\" class=\"weui-btn weui-btn_primary\">亲自试试</a>\n            </p>\n          </div>\n          <div class=\"weui-msg__extra-area\">\n              <div class=\"weui-footer\">\n                  <p class=\"weui-footer__text\">Copyright © Ming</p>\n              </div>\n          </div>\n        </div>\n    </div>\n    </div>\n\n    <script src=\"static/start.js\"></script>\n  </body>\n</html>\n\n\n\n"
  },
  {
    "path": "readme.md",
    "content": "# 茶友会 - 淘宝查券助手\n\n茶友会是自动为你查找淘宝优惠券，自动签到领飞猪里程的多功能购物助手。\n\n**强烈推荐使用 Chrome 商店安装**（这样才能获得自动更新）：\n\n<a target='_blank' rel='nofollow' href='https://chrome.google.com/webstore/detail/igedhbjllcmgidlmhclmphmhlllkibkb'>\n  <img alt='Chrome\n  web store' width='496' height='150' src='https://jjbcdn.zaoshu.so/web/cws_badge_496x150.png' />\n</a>\n\n或者直接下载的 CRX文件手动安装（非常不建议）\n\n<a href=\"http://jjb.zaoshu.so/updates/latest?app=teaclub\" target=\"_black\">\n  <img alt=\"下载 CRX 文件\" class=\"firefox\" src=\"https://jjbcdn.zaoshu.so/crx-icon.png\" width=\"32px\">\n</a>\n\n*此方法通常只适用于 Chromium 内核的国产浏览器，因为 Chrome 出于安全原因已禁止通过除 Chrome 官方商店以外的其他渠道安装拓展。*\n\n## 主要功能\n\n* 自动查询当前浏览商品的渠道优惠券\n* 自动签到领取飞猪里程\n* 自动找拼多多低价同款\n\n## 演示\n\n![自动查券](https://jjbcdn.zaoshu.so/teaclub/find-coupon.gif)\n\n\n## 重要提示\n\n1. 茶友会并非开源软件，不许可您以任何形式进行再发行，请仔细阅读[#协议和授权](https://github.com/sunoj/teaclub#%E5%8D%8F%E8%AE%AE%E5%92%8C%E6%8E%88%E6%9D%83)。\n\n2. 当前仓库是插件源代码，无法直接安装，如需安装请自行参考 [#如何开发](https://github.com/sunoj/teaclub#%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91) 编译。\n\n3. 茶友会绝对不会在任何情况下强行劫持任何网页的访问，如果发现类似问题请善用 Google 搜索并使用二分法停用插件排除，同时考虑运营商劫持的可能性。或者，为了防止茶友会的影响亦可直接卸载茶友会。故不再回复类似的 Issue。详情参考：[#安全提示](https://github.com/sunoj/teaclub#%E5%AE%89%E5%85%A8%E6%8F%90%E7%A4%BA)\n\n\n## 如何开发\n\n* 安装依赖\n> yarn\n\n* 开始开发\n>  BUILDID=1 VERSION=1.1.1 BROWSER=chrome yarn build\n\n`主要作用就是合并压缩代码，质疑代码和市场版本不一致，请先自行打包一下再对比`\n\n\n## 安全提示\n\n茶友会不会在任何情况下强行劫持访问、插入恶意代码、上传隐私信息或利用你的电脑挖矿。\n\n若你发现任何类似问题，请首先确保你使用的是商店版本，不建议在任何情况下使用第三方提供的安装包。\n\n## 系统支持\n\n目前茶友会对 Windows 和 Mac 平台的 Chrome 有较好的支持。\n\nUbuntu 有明确的兼容问题，由于作者不拥有任何 Ubuntu 设备，因此暂时无法解决。\n\n## 协议和授权\n\n茶友会并非一个开源软件，作者保留全部的权利。\n公开源代码的目的是为了让使用者能够审计代码，但是你仍然可以就以下方式合法的使用本项目的全部代码和资源：\n\n1. 个人使用\n2. 以学习目的使用全部或部分代码\n\n但你不可以：\n\n1. 将本项目的部分或全部代码和资源进行任何形式的再发行（尤其是上传到 Chrome 商店）\n2. 利用本项目的部分或全部代码和资源进行任何商业行为\n\n## 贡献代码\n\n茶友会并非一个开源项目，也不是社区共同创造，其全部功能由作者独立完成。\n\n如果你愿意放弃所有权利，并将权利无条件转让给茶友会作者，欢迎您贡献代码。\n\n## 提交反馈\n\n欢迎提交 issue，请写清楚遇到问题的原因，浏览器和操作系统环境，重现的流程。\n\n任何反馈问题的 issue 均需按照模板格式填写，否则将被直接关闭。\n\n如果有开发能力，建议在本地调试出出错的代码。\n\n## 联系作者\n\n请发邮件至：`ming@tiny.group`\n\n请勿发送功能咨询邮件，将不会收到回复。相关功能细节请自行了解。\n\n## 相关项目\n\n<h3>\n  <a href=\"https://github.com/sunoj/jjb\" target=\"_blank\">京价保</a>\n</h3>\n"
  },
  {
    "path": "src/account.js",
    "content": "import { getSetting } from './utils'\n\nexport const getLoginState = function () {\n  let loginState = {\n    pc: getSetting('login-state_pc', {\n      state: \"unknown\"\n    }),\n    m: getSetting('login-state_m', {\n      state: \"unknown\"\n    }),\n    class: \"unknown\"\n  }\n  // 处理登录状态\n  if (loginState.pc.state == 'alive' || loginState.m.state == 'alive') {\n    loginState.class = \"alive\"\n  }\n  if (loginState.pc.state == 'failed' || loginState.m.state == 'failed') {\n    loginState.class = \"failed\"\n  }\n  if (loginState.pc.time && loginState.m.time) {\n    if (new Date(loginState.pc.time) > new Date(loginState.m.time)) {\n      loginState.class = loginState.pc.state\n    } else {\n      loginState.class = loginState.m.state\n    }\n  }\n  return loginState\n}"
  },
  {
    "path": "src/background.js",
    "content": "$ = window.$ = window.jQuery = require('jquery')\nimport * as _ from \"lodash\"\nimport Logline from 'logline'\nimport Dexie from 'dexie';\nimport {DateTime} from 'luxon'\nimport {tasks, mapFrequency, getTasks, getTask} from './tasks'\nimport {rand, getSetting, saveSetting} from './utils'\nimport {getLoginState} from './account'\n\nconst db = new Dexie(\"messages\");\ndb.version(1).stores({ messages: \"++id,type,timestamp\" });\n\nasync function newMessage(messageId, data) {\n  let order = await db.messages.where('id').equals(messageId).toArray();\n  if (order && order.length > 0) return await db.messages.update(messageId, data)\n  let messageInfo = Object.assign(data, {\n    id: messageId,\n  })\n  return await db.messages.add(messageInfo);\n}\n\nasync function updateMessages() {\n  // 最多只展示最近 30 天的消息\n  let last30Day = Date.now() - 60*60*1000*24*30;\n  let messages = await db.messages.where('timestamp').above(last30Day).reverse().sortBy('timestamp')\n  saveSetting('messages', messages)\n  chrome.runtime.sendMessage({\n    action: \"messages_updated\",\n    messages: messages\n  });\n}\n\nLogline.using(Logline.PROTOCOL.INDEXEDDB)\n\nvar logger = {}\nvar mobileUAType = getSetting('uaType', 1)\n\n// 设置默认频率\n_.forEach(tasks, (task) => {\n  let frequency = getSetting(`task-${task.id}_frequency`)\n  if (!frequency) {\n    localStorage.setItem(`task-${task.id}_frequency`, task.frequency)\n  }\n})\n\n// This is to remove X-Frame-Options header, if present\nchrome.webRequest.onHeadersReceived.addListener(\n  function(info) {\n    var headers = info.responseHeaders;\n    for (var i=headers.length-1; i>=0; --i) {\n      var header = headers[i].name.toLowerCase();\n      if (header == 'x-frame-options' || header == 'frame-options') {\n          headers.splice(i, 1); // Remove header\n      }\n    }\n    return {responseHeaders: headers};\n  },\n  {\n      urls: ['*://*.taobao.com/*', '*://*.tmall.com/*', '*://*.fliggy.com/*'],\n      types: ['sub_frame']\n  },\n  ['blocking', 'responseHeaders']\n);\n\nchrome.runtime.onInstalled.addListener(function (object) {\n  let installed = localStorage.getItem('installed')\n  let uaType = localStorage.getItem('uaType')\n  if (installed) {\n    if (!uaType) {\n      localStorage.setItem('uaType', 1);\n    }\n    localStorage.setItem('oldUser', 'Y')\n    console.log(\"已经安装\")\n  } else {\n    localStorage.setItem('installed', 'Y');\n    localStorage.setItem('uaType', rand(3));\n    chrome.tabs.create({url: \"/start.html\"}, function (tab) {\n      console.log(\"茶友会安装成功！\");\n    });\n  }\n});\n\n\nvar popularPhoneUA = [\n  'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 AliApp(TB-PD/3.0.2)',\n  'Mozilla/5.0 (iPhone; CPU iPhone OS 9_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/10.2 Mobile/15E148 Safari/604.1 AliApp(TB-PD/3.0.2)',\n  'Mozilla/5.0 (iPhone9,4; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1 AliApp(TB-PD/3.0.2)',\n  'Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36 AliApp(TB-PD/2.6.5)'\n];\nchrome.webRequest.onBeforeSendHeaders.addListener(\n  function (details) {\n    for (var i = 0; i < details.requestHeaders.length; ++i) {\n      if (details.requestHeaders[i].name === 'User-Agent') {\n        details.requestHeaders[i].value = popularPhoneUA[mobileUAType];\n        break;\n      }\n    }\n    return {\n      requestHeaders: details.requestHeaders\n    };\n  }, {\n    urls: [\n      \"*://*.m.taobao.com/*\",\n    ]\n  }, ['blocking', 'requestHeaders']);\n\n\n// 判断浏览器\ntry {\n  browser.runtime.getBrowserInfo().then(function (browserInfo) {\n    localStorage.setItem('browserName', browserInfo.name);\n  })\n} catch (error) {}\n\n\nchrome.alarms.onAlarm.addListener(function( alarm ) {\n  log('background', \"onAlarm\", alarm)\n  let taskId = alarm.name.split('_')[1]\n  switch(true){\n    // 计划任务\n    case alarm.name.startsWith('runScheduleJob'):\n      runJob(taskId, true)\n      break;\n    // 定时任务\n    case alarm.name.startsWith('runJob'):\n      runJob(taskId)\n      break;\n    // 周期运行（10分钟）\n    case alarm.name == 'cycleTask':\n      findJobs()\n      runJob()\n      updateIcon()\n      break;\n    case alarm.name.startsWith('clearIframe'):\n      resetIframe(taskId || 'iframe')\n      break;\n    case alarm.name.startsWith('destroyIframe'):\n      $(\"#\" + taskId).remove();\n      break;\n    case alarm.name.startsWith('closeTab'):\n      try {\n        chrome.tabs.get(taskId, (tab) => {\n          if (tab) {\n            chrome.tabs.remove(tab.id)\n          }\n        })\n      } catch (e) {}\n      break;\n    case alarm.name == 'reload':\n      chrome.runtime.reload()\n      chrome.alarms.clearAll()\n      // 保留3天内的log\n      Logline.keep(3);\n      break;\n  }\n})\n\n// 保存任务栈\nfunction saveJobStack(jobStack) {\n  jobStack = _.uniq(jobStack)\n  localStorage.setItem('jobStack', JSON.stringify(jobStack));\n}\n\n\n\nfunction scheduleJob(task) {\n  let hour = DateTime.local().hour;\n  for (var i = 0, len = task.schedule.length; i < len; i++) {\n    let scheduledHour = task.schedule[i]\n    if (scheduledHour > hour) {\n      let scheduledTime = DateTime.local().set({\n        hour: scheduledHour,\n        minute: rand(2) - 1,\n        second: rand(55)\n      }).valueOf()\n      chrome.alarms.create('runScheduleJob_' + task.id, {\n        when: scheduledTime\n      })\n      log('background', \"schedule job created\", {\n        job: task,\n        time: scheduledHour,\n        when: scheduledTime\n      })\n      break;\n    }\n  }\n}\n\n// 寻找乔布斯\nfunction findJobs() {\n  let jobStack = getSetting('jobStack', [])\n  let taskList = getTasks()\n  taskList.forEach(function(task) {\n    if (task.suspended) {\n      return console.log(task.title, '由于账号未登录已暂停运行')\n    }\n    // 如果任务有时间安排，则把任务安排到最近的下一个时段\n    if (task.schedule) {\n      return chrome.alarms.get('runScheduleJob_' + task.id, function (alarm) {\n        if (!alarm || alarm.scheduledTime < Date.now()) {\n          return scheduleJob(task)\n        } else {\n          console.log(\"job already scheduled \", alarm)\n        }\n      })\n    }\n    switch(task.frequency){\n      case '2h':\n        // 如果从没运行过，或者上次运行已经过去超过2小时，那么需要运行\n        if (!task.last_run_at || (DateTime.local() > DateTime.fromMillis(task.last_run_at).plus({ hours: 2 }))) {\n          jobStack.push(task.id)\n        }\n        break;\n      case '5h':\n        // 如果从没运行过，或者上次运行已经过去超过5小时，那么需要运行\n        if (!task.last_run_at || (DateTime.local() > DateTime.fromMillis(task.last_run_at).plus({ hours: 5 }))) {\n          jobStack.push(task.id)\n        }\n        break;\n      case 'daily':\n        // 如果从没运行过，或者上次运行不在今天，或者是签到任务但未完成\n        if (!task.last_run_at || !(DateTime.local().hasSame(DateTime.fromMillis(task.last_run_at), 'day')) || (task.checkin && !task.checked)) {\n          jobStack.push(task.id)\n        }\n        break;\n      default:\n        console.log('ok, never run ', task.title)\n    }\n  });\n  saveJobStack(jobStack)\n}\n\nfunction log(type, message, details) {\n  if (!logger[type]) {\n    logger[type] = new Logline(type)\n  }\n  logger[type].info(message, details)\n  console.log(new Date(), type, message, details)\n}\n\nfunction resetIframe(domId) {\n  $(\"#\" + domId).remove();\n  let iframeDom = `<iframe id=\"${domId}\" width=\"400 px\" height=\"600 px\" src=\"\"></iframe>`;\n  $('body').append(iframeDom);\n}\n\nfunction incrementUsage(task) {\n  let year = new Date().getFullYear()\n  let today = DateTime.local().toFormat(\"o\")\n  let hour = new Date().getHours()\n  saveSetting(`temporary:usage-${task.id}_${year}d:${today}:h:${hour}`, task.usage.hour + 1)\n  saveSetting(`temporary:usage-${task.id}_${year}d:${today}`, task.usage.daily  + 1)\n}\n\n// 执行组织交给我的任务\nfunction runJob(taskId, force = false) {\n  // 不在凌晨阶段运行非强制任务\n  if (DateTime.local().hour < 6 && !force) {\n    return console.log('Silent Night')\n  }\n  log('background', \"run job\", {\n    jobId: taskId,\n    force: force\n  })\n  // 如果没有指定任务ID 就从任务栈里面找一个\n  if (!taskId) {\n    let jobStack = getSetting('jobStack', [])\n    if (jobStack && jobStack.length > 0) {\n      taskId = jobStack.shift();\n      saveJobStack(jobStack)\n    } else {\n      return log('info', new Date(), '好像没有什么事需要我做...')\n    }\n  }\n  let task = getTask(taskId)\n\n  // 如果任务已暂停\n  if (task.pause) {\n    return log('job', task.title, '由于运行次数超限而被暂停')\n  }\n\n  // 如果任务暂停或者已经完成\n  if ((task.suspended || task.checked) && !force) {\n    return log('job', task.title, '由于账号未登录已暂停运行')\n  }\n  if (task && (task.frequency != 'never' || force)) {\n    log('background', \"run\", task)\n    incrementUsage(task)\n    if (task.mode == 'iframe') {\n      openByIframe(task.url, 'job')\n    } else {\n      chrome.tabs.create({\n        index: 1,\n        url: task.url,\n        active: false,\n        pinned: true\n      }, function (tab) {\n        // 将标签页静音\n        chrome.tabs.update(tab.id, {\n          muted: true\n        }, function (result) {\n          log('background', \"muted tab\", result)\n        })\n        chrome.alarms.create('closeTab_'+tab.id, {delayInMinutes: 3})\n      })\n    }\n  }\n}\n\nfunction openByIframe(src, type, delayTimes = 0) {\n  // 加载新的任务\n  let iframeId = \"iframe\"\n  let keepMinutes = 3\n  if (type == 'temporary') {\n    iframeId = 'iframe' + Math.random().toString(36).substring(7);\n    keepMinutes = 1\n  }\n  // 当前任务过多则等待\n  if ($('iframe').length > 5 && delayTimes < 6) {\n    setTimeout(() => {\n      openByIframe(src, type, delayTimes + 1)\n    }, (10 + rand(10)) * 1000);\n    return console.log('too many iframe pages', src, delayTimes)\n  }\n  // 运行\n  resetIframe(iframeId)\n  $(\"#\" + iframeId).attr('src', src)\n  // 设置重置任务\n  chrome.alarms.create(`${(type == 'temporary' ? 'destroyIframe' : 'clearIframe')}_${iframeId}`, {\n    delayInMinutes: keepMinutes\n  })\n}\n\nfunction updateUnreadCount(change = 0) {\n  let lastUnreadCount = localStorage.getItem('unreadCount') || 0\n  let unreadCount = parseInt(Number(lastUnreadCount) + change)\n  if (unreadCount < 0) {\n    unreadCount = 0\n  }\n  localStorage.setItem('unreadCount', unreadCount);\n  if (unreadCount > 0) {\n    let unreadCountText = unreadCount.toString()\n    if (unreadCount > 100) {\n      unreadCountText = '99+'\n    }\n    chrome.browserAction.setBadgeText({ text: unreadCountText });\n    chrome.browserAction.setBadgeBackgroundColor({ color: \"#4caf50\" });\n  } else {\n    chrome.browserAction.setBadgeText({ text: \"\" });\n  }\n}\n\n$( document ).ready(function() {\n  log('background', \"document ready\")\n  // 每20分钟运行一次定时任务\n  chrome.alarms.create('cycleTask', {\n    periodInMinutes: 20\n  })\n\n  // 每600分钟完全重载\n  chrome.alarms.create('reload', {periodInMinutes: 600})\n\n  // 载入后马上运行一次任务查找\n  findJobs()\n\n  // 载入显示未读数量\n  updateUnreadCount()\n\n  // 加载任务参数\n  loadSettingsToLocalStorage('teaclub:task-parameters')\n  loadSettingsToLocalStorage('teaclub:action-links')\n\n  // 加载推荐设置\n  loadRecommendSettingsToLocalStorage()\n})\n\n\nfunction openWebPageAsMobile(url) {\n  chrome.windows.create({\n    width: 420,\n    height: 800,\n    url: url,\n    type: \"popup\"\n  });\n}\n\n// 点击通知\nchrome.notifications.onClicked.addListener(function (notificationId) {\n  if (notificationId.split('_').length > 0) {\n    let type = notificationId.split('_')[1]\n    if (type && type.length > 1) {\n      switch (type) {\n        case 'fliggy':\n          chrome.tabs.create({\n            url: \"https://teaclub.zaoshu.so/sites/fliggy\"\n          })\n          break;\n        default:\n          chrome.tabs.create({\n            url: \"https://teaclub.zaoshu.so/sites/taobao\"\n          })\n      }\n    }\n  }\n})\n\n// 根据登录状态调整图标显示\nfunction updateIcon() {\n  let loginState = getLoginState()\n  switch (loginState.class) {\n    case 'alive':\n      chrome.browserAction.getBadgeText({}, function (text){\n        if (text == \"X\" || text == \" ! \") {\n          chrome.browserAction.setBadgeText({\n            text: \"\"\n          });\n          chrome.browserAction.setTitle({\n            title: \"茶友会\"\n          })\n        }\n      })\n      chrome.browserAction.setIcon({\n        path : {\n          \"19\": \"static/image/icon@19.png\",\n          \"38\": \"static/image/icon@38.png\"\n        }\n      });\n      chrome.contextMenus.removeAll();\n\n      break;\n    case 'failed':\n      chrome.browserAction.setBadgeBackgroundColor({\n        color: [190, 190, 190, 230]\n      });\n      chrome.browserAction.setBadgeText({\n        text: \"X\"\n      });\n      chrome.browserAction.setTitle({\n        title: \"账号登录失效\"\n      })\n      chrome.contextMenus.removeAll();\n      chrome.contextMenus.create({\n        title: \"账号登录失效，点击登录\",\n        contexts: [\"browser_action\"],\n        onclick: function() {\n          openLoginPage()\n        }\n      });\n      break;\n    case 'warning':\n      chrome.browserAction.setBadgeBackgroundColor({\n        color: \"#EE7E1B\"\n      });\n      chrome.browserAction.setBadgeText({\n        text: \" ! \"\n      });\n      chrome.contextMenus.removeAll();\n      chrome.contextMenus.create({\n        title: \"账号登录失效，点击登录\",\n        contexts: [\"browser_action\"],\n        onclick: function() {\n          openLoginPage()\n        }\n      });\n      break;\n    default:\n      break;\n  }\n}\n\nfunction openLoginPage() {\n  chrome.tabs.create({\n    url: \"https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm\"\n  })\n}\n\n// 保存登录状态\nfunction saveLoginState(loginState) {\n  let previousState = getLoginState()\n  localStorage.setItem('login-state_' + loginState.type, JSON.stringify({\n    time: new Date(),\n    message: loginState.content || loginState.message,\n    state: loginState.state\n  }));\n  chrome.runtime.sendMessage({\n    action: \"loginState_updated\",\n    data: loginState\n  });\n  // 如果登录状态从失败转换到了在线\n  if (previousState.class != 'alive' && loginState.state == \"alive\") {\n    console.log('user account turn alive')\n    setTimeout(() => {\n      findJobs()\n    }, 5000);\n    setTimeout(() => {\n      runJob()\n    }, 15000);\n  }\n}\n\n// 浏览器通知（合并）\n// mute_night\nfunction sendChromeNotification(id, content) {\n  let hour = DateTime.local().hour;\n  let muteNight = getSetting('mute_night');\n  if (muteNight && hour < 6) {\n    log('background', 'mute_night', content);\n  } else {\n    chrome.notifications.create(id, content)\n    log('message', id, content);\n  }\n}\n\n\nfunction runTask(msg, sendResponse) {\n  let task = getTask(msg.taskId)\n  // set 临时运行\n  localStorage.setItem('temporary_job' + task.id + '_frequency', 'onetime');\n  // 任务因为频率受限无法运行\n  if (task.pause) {\n    sendChromeNotification(new Date().getTime().toString(), {\n      type: \"basic\",\n      title: \"任务因为频率受限无法运行\",\n      message: task.title + \"已达到最大时段频率，每小时：\" + task.rateLimit.hour,\n      iconUrl: 'static/image/128.png'\n    })\n    sendResponse({\n      result: \"pause\",\n      message: \"任务因为频率受限无法运行\"\n    })\n  } else {\n    runJob(task.id, true)\n    sendResponse({\n      result: \"success\"\n    })\n    if (!msg.hideNotice) {\n      sendChromeNotification(new Date().getTime().toString(), {\n        type: \"basic\",\n        title: \"正在重新运行\" + task.title,\n        message: \"任务运行大约需要2分钟，如果有情况我再叫你（请勿连续运行）\",\n        iconUrl: 'static/image/128.png'\n      })\n    }\n  }\n}\n\nfunction markCheckinStatus(msg) {\n  let task = getTask(msg.taskId)\n  if (task) {\n    let year = new Date().getFullYear()\n    let checkinKey = `checkin_${task.key}`\n    let currentStatus = getSetting(checkinKey, null)\n    let data = {\n      date: DateTime.local().toFormat(\"o\"),\n      time: new Date(),\n      value: msg.value\n    }\n    if (msg.month) {\n      localStorage.setItem(`order-fliggy-${year}-${msg.month}`, 'Y');\n    }\n    if (msg.orderId) {\n      localStorage.setItem(`order-fliggy_${msg.orderId}`, 'Y');\n    }\n    if (currentStatus && currentStatus.date == DateTime.local().toFormat(\"o\")) {\n      console.log('已经记录过今日签到状态了')\n    } else {\n      localStorage.setItem(checkinKey, JSON.stringify(data));\n      return data\n    }\n  }\n}\n\nfunction updateRunStatus(msg) {\n  let task = getTask(msg.taskId)\n  if (task) {\n    localStorage.setItem('task-' + task.id + '_lasttime', new Date().getTime())\n    saveLoginState({\n      content: task.title + \"成功运行\",\n      state: \"alive\",\n      type: msg.mode || task.type[0]\n    })\n    // 如果任务周期小于10小时，且不是计划任务，则安排下一次运行\n    if (mapFrequency[task.frequency] < 600 && !task.schedule) {\n      chrome.alarms.create('runJob_' + task.id, {\n        delayInMinutes: mapFrequency[task.frequency]\n      })\n    }\n  }\n}\n\n// 加载任务参数\nfunction loadSettingsToLocalStorage(key) {\n  $.getJSON(`https://teaclub.zaoshu.so/setting/${key}`, function (json) {\n    saveSetting(key, json)\n  })\n}\n\n// 加载推荐设置\nfunction loadRecommendSettingsToLocalStorage() {\n  $.getJSON(\"https://teaclub.zaoshu.so/setting/teaclub:recommend\", function (json) {\n    if (json.displayPopup) {\n      saveSetting('displayPopup', json.displayPopup)\n    }\n    if (json.events) {\n      saveSetting('events', json.events)\n    }\n    if (json.announcements && json.announcements.length > 0) {\n      saveSetting('announcements', json.announcements)\n    }\n    if (json.promotions) {\n      saveSetting('promotions', json.promotions)\n    }\n    if (json.recommendedLinks && json.recommendedLinks.length > 0) {\n      saveSetting('recommendedLinks', json.recommendedLinks)\n    } else {\n      localStorage.removeItem('recommendedLinks')\n    }\n    if (json.uninstallURL) {\n      chrome.runtime.setUninstallURL(json.uninstallURL)\n    }\n    if (json.recommendServices && json.recommendServices.length > 0) {\n      saveSetting('recommendServices', json.recommendServices)\n    }\n  });\n}\n\n\nfunction sendMessageToPage(targetPage, data) {\n  chrome.tabs.sendMessage(targetPage.tab.id, data, {}, function (response) {\n    console.log('send message to tabs response', data, response)\n  })\n}\n\nfunction timeoutPromise(promise, ms) {\n  return new Promise(function(resolve, reject) {\n    setTimeout(function() {\n      reject(new Error(\"timeout\"))\n    }, ms)\n    promise.then(resolve, reject)\n  })\n}\n\n// 查找优惠券\nasync function searchCoupon(params) {\n  try {\n    let response = await timeoutPromise(fetch(`https://teaclub.zaoshu.so/coupon/search?sku=${params.sku}&keyword=${params.title}&merchant=${params.merchant}`), 10000)\n    let details = await response.json();\n    return details;\n  } catch (error) {\n    console.error(error)\n    return null\n  }\n}\n\n// 处理消息通知\nchrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {\n  if (!msg.action) {\n    msg.action = msg.text\n  }\n  let loginState = getLoginState()\n  log('msg', new Date(), msg);\n  switch(msg.action){\n    // 保存登录状态\n    case 'saveLoginState':\n      saveLoginState(msg)\n      break;\n    // 获取登录状态\n    case 'getLoginState':\n      sendResponse(loginState)\n      break;\n    // 打开登录页面\n    case 'openLogin':\n      openLoginPage()\n      break;\n    // 保存变量值\n    case 'setVariable':\n      localStorage.setItem(msg.key, JSON.stringify(msg.value));\n      break;\n    // 获取设置\n    case 'getSetting':\n      let setting = getSetting(msg.content)\n      let temporarySetting = localStorage.getItem('temporary_' + msg.content)\n      // 如果存在临时设置\n      if (temporarySetting) {\n        // 临时设置5分钟失效\n        setTimeout(() => {\n          localStorage.removeItem('temporary_' + msg.content)\n        }, 60*5*1000);\n        return sendResponse(temporarySetting)\n      }\n      sendResponse(setting)\n      break;\n    // 领取订单的里程\n    case 'getOrderFliggy':\n      let orderStatus = localStorage.getItem(`order-fliggy_${msg.orderId}`)\n      console.log('getOrderFliggy', msg.orderId, orderStatus)\n      // 如果没有领取过\n      if (!(orderStatus && orderStatus == 'Y')) {\n        let url = `https://www.fliggy.com/mytrip/?tvm=tcd&orderId=${msg.orderId}`\n        setTimeout(() => {\n          openByIframe(url, 'temporary')\n        }, rand(20) * 1000);\n      }\n      sendResponse({\n        working: true\n      })\n      break;\n    case 'openUrlAsMobile':\n      openWebPageAsMobile(msg.url)\n      break;\n    case 'option':\n      localStorage.setItem(''+msg.title, msg.content);\n      break;\n    // 查询消息列表\n    case 'getMessages':\n      setTimeout(async () => {\n        await updateMessages()\n      }, 50);\n      break;\n    // 手动运行任务\n    case 'runTask':\n      runTask(msg, sendResponse)\n      break;\n    // 签到通知\n    case 'checkin_notice':\n      let mute_checkin = getSetting('mute_checkin')\n      if (mute_checkin && mute_checkin == 'checked' && !msg.test) {\n        console.log('checkin', msg)\n      } else {\n        let icon = 'static/image/coin.png'\n        let type = \"basic\"\n        if (msg.type == 'mileage') {\n          icon = 'static/image/mileage.png'\n          type = 'fliggy'\n        }\n        sendChromeNotification( new Date().getTime().toString() + '_' + msg.reward, {\n          type: type,\n          title: msg.title,\n          message: msg.content,\n          iconUrl: icon\n        })\n      }\n      break;\n    // 签到状态\n    case 'markCheckinStatus':\n      let result = markCheckinStatus(msg)\n      sendResponse({\n        result\n      })\n      break;\n    // 更新运行状态\n    case 'updateRunStatus':\n      updateRunStatus(msg)\n      sendResponse({\n        result: true\n      })\n      break;\n    // 查询优惠券\n    case 'queryCoupon':\n      var disable_same_goods = getSetting('disable_same_goods')\n      setTimeout(async () => {\n        let result = await searchCoupon(msg.params)\n        if (disable_same_goods) {\n          result.similarGoods = null\n        }\n        sendMessageToPage(sender, {\n          type: \"couponInfo\",\n          content: result\n        })\n      }, 50);\n      sendResponse({\n        result: true\n      })\n      break;\n    case 'coupon':\n      var coupon = msg.content\n      var mute_coupon = getSetting('mute_coupon')\n      if (mute_coupon && mute_coupon == 'checked') {\n        console.log('coupon', msg)\n      } else {\n        sendChromeNotification( new Date().getTime().toString() + \"_coupon_\" + coupon.batch, {\n          type: \"basic\",\n          title: msg.title,\n          message: coupon.name + coupon.price,\n          isClickable: true,\n          iconUrl: 'static/image/coupon.png'\n        })\n      }\n      break;\n    case 'clearUnread':\n      updateUnreadCount(-999)\n      break;\n    case 'myTab':\n      sendResponse({\n        tab: sender.tab\n      });\n      break;\n    default:\n      console.log(\"Received %o from %o, frame\", msg, sender.tab, sender.frameId);\n  }\n  // 更新图标\n  updateIcon()\n  // 保存消息\n  switch (msg.action) {\n    case 'coupon':\n    case 'notice':\n    case 'checkin_notice':\n      if (msg.test) {\n        break;\n      }\n      let message = {\n        type: msg.type || msg.action, // 通知的类型\n        batch: msg.batch, // 批次，通常是优惠券的属性\n        reward: msg.reward, // 奖励的类型\n        unit: msg.unit || msg.reward || msg.batch, // 奖励的单位\n        value: msg.value, // 奖励的数量\n        title: msg.title,\n        content: msg.content,\n        timestamp: Date.now()\n      }\n      let uuid = msg.uuid || Date.now()\n      updateUnreadCount(1)\n      setTimeout(async () => {\n        await newMessage(uuid, message);\n      }, 50);\n      setTimeout(async () => {\n        await updateMessages()\n      }, 3000);\n      break;\n  }\n\n  if (msg.text != 'saveAccount') {\n    log('message', msg.text, msg);\n  }\n  // 如果消息 300ms 未被回复\n  return true\n});\n\nLogline.keep(3);"
  },
  {
    "path": "src/components/app.vue",
    "content": "<template>\n  <div>\n    <div class=\"main-container\">\n      <div class=\"settings\">\n        <div class=\"weui-tab\">\n          <div :class=\"`${scienceOnline} weui-navbar`\">\n            <div class=\"weui-navbar__item weui-bar__item_on\" data-type=\"frequency_settings\">任务设置</div>\n            <div class=\"weui-navbar__item\" data-type=\"other_settings\">高级设置</div>\n          </div>\n          <div class=\"weui-tab__panel\">\n            <form\n              id=\"settings\"\n              data-persist=\"garlic\"\n              data-domain=\"true\"\n              data-destroy=\"false\"\n              method=\"POST\"\n            >\n              <div class=\"frequency_settings settings_box\">\n                <div class=\"weui-cells weui-cells_form\">\n                  <div\n                    class=\"weui-cell weui-cell_select weui-cell_select-after\"\n                    v-for=\"task in taskList\"\n                    :key=\"task.id\"\n                  >\n                    <div class=\"weui-cell__bd job-m\">\n                      <span\n                        data-microtip-position=\"bottom-right\"\n                        role=\"tooltip\"\n                        :aria-label=\"task.description\"\n                      >\n                        <a\n                          v-if=\"task.platform == 'm'\"\n                          class=\"openMobilePage\"\n                          :data-url=\"task.baseUrl || task.url\"\n                        >{{task.title}}</a>\n                        <a v-else :href=\"task.url\" target=\"_blank\">{{task.title}}</a>\n                      </span>\n                      <span\n                        v-show=\"task.suspended && !task.checked\"\n                        data-microtip-position=\"bottom-right\"\n                        role=\"tooltip\"\n                        aria-label=\"未知登录状态，请点击任务名称手动运行\"\n                      >\n                        <i class=\"job-state weui-icon-waiting-circle\"></i>\n                      </span>\n                      <span\n                        v-show=\"task.checked\"\n                        data-microtip-position=\"bottom-right\"\n                        role=\"tooltip\"\n                        :aria-label=\"task.checkin_description\"\n                      >\n                        <i class=\"today weui-icon-success-circle\"></i>\n                      </span>\n                      <i\n                        v-show=\"!task.checked && !task.suspended\"\n                        @click=\"retryTask(task)\"\n                        class=\"reload-icon\"\n                        data-microtip-position=\"bottom-right\"\n                        role=\"tooltip\"\n                        :aria-label=\"task.last_run_description\"\n                      ></i>\n                    </div>\n                    <div class=\"weui-cell__bd\">\n                      <select class=\"weui-select\" v-auto-save :name=\"`task-${task.id}_frequency`\">\n                        <option\n                          v-for=\"option in task.frequencyOption\"\n                          :value=\"option\"\n                          :key=\"option\"\n                        >{{ frequencyOptionText[option] }}</option>\n                      </select>\n                    </div>\n                  </div>\n                </div>\n                <div class=\"other_actions\">\n                  <div class=\"recommendation\">\n                    <h3 style=\"text-align: center;color: #666;\">服务推荐</h3>\n                    <p class=\"recommendServices\">\n                      <span\n                        :class=\"service.class\"\n                        v-for=\"service in recommendServices\"\n                        :key=\"service.title\"\n                      >\n                        <a\n                          target=\"_blank\"\n                          data-microtip-position=\"top\"\n                          role=\"tooltip\"\n                          :aria-label=\"service.description\"\n                          :href=\"service.link\"\n                        >{{service.title}}</a>\n                      </span>\n                    </p>\n                    <div class=\"recommendedLink\">\n                      <p v-for=\"link in recommendedLinks\" :key=\"link.title\">\n                        <a\n                          v-if=\"link.mobile\"\n                          class=\"openMobilePage\"\n                          :style=\"link.style\"\n                          :data-url=\"link.url\"\n                        >{{link.title}}</a>\n                        <a\n                          v-else\n                          :href=\"link.url\"\n                          :style=\"link.style\"\n                          class=\"weui-form-preview__btn weui-form-preview__btn_primary\"\n                          target=\"_blank\"\n                        >{{link.title}}</a>\n                      </p>\n                    </div>\n                  </div>\n                </div>\n                <div class=\"tips bottom-tips\"></div>\n              </div>\n              <div class=\"other_settings settings_box\" style=\"display: none\">\n                <div class=\"weui-cells weui-cells_form\">\n                  <div class=\"weui-cell weui-cell_switch\">\n                    <div class=\"weui-cell__bd\">停用自动找券</div>\n                    <div class=\"weui-cell__ft\">\n                      <input\n                        class=\"weui-switch\"\n                        v-auto-save\n                        type=\"checkbox\"\n                        name=\"disable_find_coupon\"\n                      >\n                    </div>\n                  </div>\n                  <div class=\"weui-cell weui-cell_switch\">\n                    <div class=\"weui-cell__bd\">停止“找同款”</div>\n                    <div class=\"weui-cell__ft\">\n                      <input\n                        class=\"weui-switch\"\n                        v-auto-save\n                        type=\"checkbox\"\n                        name=\"disable_same_goods\"\n                      >\n                    </div>\n                  </div>\n                  <div class=\"weui-cell weui-cell_switch\">\n                    <div class=\"weui-cell__bd\">不再提示签到通知</div>\n                    <div class=\"weui-cell__ft\">\n                      <input class=\"weui-switch\" v-auto-save type=\"checkbox\" name=\"mute_checkin\">\n                    </div>\n                  </div>\n                  <div class=\"weui-cell weui-cell_switch\">\n                    <div class=\"weui-cell__bd\">\n                      <span\n                        data-microtip-position=\"top\"\n                        role=\"tooltip\"\n                        aria-label=\"开启后不再晚上12点至凌晨6点发送浏览器通知\"\n                      >开启夜晚防打扰</span>\n                    </div>\n                    <div class=\"weui-cell__ft\">\n                      <input class=\"weui-switch\" type=\"checkbox\" v-auto-save name=\"mute_night\">\n                    </div>\n                  </div>\n                </div>\n                <div class=\"other_actions\">\n                  <p\n                    class=\"tips\"\n                    style=\"text-align: center;\"\n                  >茶友会当前版本为预览版，相关功能并不完善，若有功能建议或反馈，请写邮件至：ming@tiny.group</p>\n                </div>\n                <p\n                  class=\"text-tips version showChangelog\"\n                  @click=\"showChangelog\"\n                  data-microtip-position=\"top\"\n                  role=\"tooltip\"\n                  aria-label=\"点击查看版本更新记录\"\n                >\n                  当前版本：{{currentVersion}}\n                  <span\n                    class=\"weui-badge weui-badge_dot\"\n                    v-if=\"newChangelog\"\n                  ></span>\n                  <span class=\"weui-badge new-version\" v-if=\"newVersion\">有新版</span>\n                </p>\n              </div>\n            </form>\n          </div>\n        </div>\n        <div class=\"bottom-box weui-tabbar\">\n          <div\n            class=\"avatar\"\n            data-microtip-position=\"top-right\"\n            role=\"tooltip\"\n            :aria-label=\"loginStateDescription\"\n          >\n            <a\n              id=\"loginState\"\n              :class=\"` login-state ${loginState.class}`\"\n              :target=\"loginState.class != 'alive' ? '_blank' : '_self'\"\n              :href=\"loginState.class != 'alive' ? 'https://i.taobao.com/my_taobao.htm' : ''\"\n            ></a>\n          </div>\n          <links></links>\n        </div>\n      </div>\n      <div class=\"contents\">\n        <div class=\"weui-tab\">\n          <div class=\"weui-navbar\">\n            <div\n              :class=\"`weui-navbar__item ${contentType == 'messages' ? 'weui-bar__item_on' : ''}`\"\n              @click=\"switchContentType('messages')\"\n            >\n              最近通知\n              <span class=\"weui-badge\" v-if=\"unreadCount > 0\">{{unreadCount}}</span>\n            </div>\n            <div\n              :class=\"`weui-navbar__item zaoshu-tab ${contentType == 'discounts' ? 'weui-bar__item_on' : ''}`\"\n              @click=\"switchContentType('discounts')\"\n            >\n              <img src=\"../../static/image/zaoshu.png\" alt=\"\" class=\"zaoshu-icon\">\n              枣树集惠\n              <span\n                class=\"weui-badge weui-badge_dot new-discounts\"\n                v-if=\"newDiscounts\"\n              ></span>\n            </div>\n          </div>\n          <div class=\"weui-tab__panel\">\n            <div id=\"messages\" v-if=\"contentType == 'messages'\" class=\"contents-box messages\">\n              <div class=\"weui-cells message-items\" v-if=\"messages && messages.length > 0\">\n                <li v-for=\"(message, index) in messages\" :key=\"index\">\n                  <div\n                    :class=\"`weui-panel__bd message-item type-${message.type}`\"\n                    v-show=\"!selectedTab || selectedTab == message.type\"\n                  >\n                    <div class=\"weui-media-box weui-media-box_text\">\n                      <h4 class=\"weui-media-box__title message\">\n                        <i :class=\"`${message.type} ${message.reward}`\"></i>\n                        {{message.title}}\n                      </h4>\n                      <p class=\"weui-media-box__desc\">{{message.content}}</p>\n                      <ul class=\"weui-media-box__info\">\n                        <li class=\"weui-media-box__info__meta\">时间: {{message.time}}</li>\n                      </ul>\n                    </div>\n                  </div>\n                </li>\n              </div>\n              <div class=\"no_message\" v-else>暂时还没有未读消息</div>\n            </div>\n            <discounts v-if=\"contentType == 'discounts'\"/>\n          </div>\n        </div>\n        <div class=\"bottom\">\n          <div class=\"weui-tabbar\">\n            <a\n              class=\"showChangelog weui-tabbar__item\"\n              @click=\"showChangelog\"\n              data-microtip-position=\"top\"\n              role=\"tooltip\"\n              aria-label=\"查看茶友会最近更新记录\"\n              style=\"position: relative;\"\n            >\n              <img src=\"../../static/image/update.png\" alt=\"\" class=\"weui-tabbar__icon\">\n              <p class=\"weui-tabbar__label\">\n                最近更新\n                <span\n                  class=\"weui-badge weui-badge_dot\"\n                  style=\"position: absolute;top: 0;right: 4em;\"\n                  v-if=\"newChangelog\"\n                ></span>\n                <span\n                  class=\"weui-badge\"\n                  style=\"position: absolute;top: -.4em;right: 2em;\"\n                  v-if=\"newVersion\"\n                >有新版</span>\n              </p>\n            </a>\n            <a\n              id=\"openGithub\"\n              class=\"weui-tabbar__item\"\n              href=\"https://github.com/sunoj/teaclub\"\n              data-microtip-position=\"top\"\n              role=\"tooltip\"\n              aria-label=\"点击查看本插件的全部代码\"\n              target=\"_blank\"\n            >\n              <img src=\"../../static/image/github.png\" alt=\"\" class=\"weui-tabbar__icon\">\n              <p class=\"weui-tabbar__label\">源代码</p>\n            </a>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"dialogs\">\n      <guide v-if=\"showGuide\" :login-state=\"loginState\"></guide>\n      <popup v-if=\"showPopup\" @close=\"showPopup = false\"></popup>\n    </div>\n  </div>\n</template>\n\n<script>\nimport * as _ from \"lodash\";\nimport weui from \"weui.js\";\nimport Vue from \"vue\";\n\nimport { DateTime } from \"luxon\";\nimport { getLoginState } from \"../account\";\nimport { tasks, frequencyOptionText, getTasks } from \"../tasks\";\nimport { getSetting, versionCompare, readableTime } from \"../utils\";\nimport { stateText, recommendServices } from \"../variables\";\n\nVue.directive(\"autoSave\", {\n  bind(el, binding, vnode) {\n    function revertValue(el) {\n      let current = getSetting(el.name, null);\n      if (el.type == \"checkbox\") {\n        if (current == \"checked\") {\n          el.checked = true;\n        } else {\n          el.checked = false;\n        }\n      } else if (el.type == \"select-one\") {\n        el.value = current || el.options[0].value;\n      } else {\n        el.value = current;\n      }\n    }\n    function saveToLocalStorage(el, binding) {\n      if (el.type == \"checkbox\") {\n        if (el.checked) {\n          localStorage.setItem(el.name, \"checked\");\n        } else {\n          localStorage.removeItem(el.name);\n        }\n      } else {\n        localStorage.setItem(el.name, el.value);\n      }\n      weui.toast(\"设置已保存\", 500);\n    }\n    revertValue(el);\n    el.addEventListener(\"change\", function(event) {\n      if (binding.value && binding.value.notice && el.checked) {\n        weui.confirm(\n          binding.value.notice,\n          function() {\n            saveToLocalStorage(el, binding);\n          },\n          function() {\n            event.preventDefault();\n            setTimeout(() => {\n              revertValue(el);\n            }, 50);\n          },\n          {\n            title: \"选项确认\"\n          }\n        );\n      } else {\n        saveToLocalStorage(el, binding);\n      }\n    });\n  }\n});\n\nimport loading from \"./loading.vue\";\nimport discounts from \"./discounts.vue\";\nimport guide from \"./guide.vue\";\nimport popup from \"./popup.vue\";\nimport links from \"./links.vue\";\n\nexport default {\n  name: \"App\",\n  components: { loading, discounts, guide, popup, links },\n  data() {\n    return {\n      taskList: [],\n      messages: [],\n      skuPriceList: {},\n      recommendedLinks: getSetting(\"recommendedLinks\", []),\n      stateText: stateText,\n      newDiscounts: false,\n      showPopup: true,\n      frequencyOptionText: frequencyOptionText,\n      recommendServices: getSetting(\"recommendServices\", recommendServices),\n      currentVersion: process.env.VERSION,\n      contentType: \"messages\",\n      newChangelog:\n        versionCompare(\n          getSetting(\"changelog_version\", \"2.0\"),\n          process.env.VERSION\n        ) < 0,\n      hiddenPromotionIds: getSetting(\"hiddenPromotionIds\", []),\n      unreadCount: getSetting(\"unreadCount\", null),\n      selectedTab: null,\n      scienceOnline: false,\n      newVersion: getSetting(\"newVersion\", null),\n      loginStateDescription: \"未能获取登录状态\",\n      olduser: getSetting(\"oldUser\", false),\n      showGuideAt: getSetting(\"showGuideAt\", false),\n      loginState: {\n        default: true,\n        m: {\n          state: \"unknown\"\n        },\n        pc: {\n          state: \"unknown\"\n        }\n      },\n      discountTab: \"featured\"\n    };\n  },\n  computed: {\n    showGuide: function() {\n      if (!this.olduser && !this.showGuideAt) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n  },\n  mounted: async function() {\n    // 准备数据\n    this.getTaskList();\n\n    // 渲染通知\n    setTimeout(() => {\n      this.renderMessages();\n    }, 50);\n\n    // 查询最新优惠\n    setTimeout(() => {\n      this.getLastDiscount();\n    }, 100);\n\n    // 测试是否科学上网\n    setTimeout(() => {\n      this.tryGoogle();\n    }, 200);\n\n    this.dealWithLoginState();\n\n    // 接收消息\n    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {\n      switch (message.action) {\n        case \"messages_updated\":\n          this.renderMessages(message.messages);\n          break;\n        case \"loginState_updated\":\n          this.dealWithLoginState();\n          setTimeout(() => {\n            this.getTaskList();\n          }, 1000);\n          break;\n        default:\n          break;\n      }\n    });\n  },\n  methods: {\n    tryGoogle: async function() {\n      let response = await fetch(\n        \"https://www.googleapis.com/discovery/v1/apis?name=abusiveexperiencereport\"\n      );\n      if (response.status == \"200\") {\n        this.scienceOnline = true;\n      } else {\n        this.scienceOnline = false;\n      }\n    },\n    switchContentType: function(type) {\n      this.contentType = type;\n      switch (type) {\n        case \"messages\":\n          this.renderMessages();\n          this.readMessages();\n          break;\n        case \"discounts\":\n          this.readDiscounts();\n          break;\n        default:\n          break;\n      }\n    },\n    getLastDiscount: async function() {\n      let response = await fetch(\"https://teaclub.zaoshu.so/discount/last\");\n      let lastDiscount = await response.json();\n      let readDiscountAt = localStorage.getItem(\"readDiscountAt\");\n      if (\n        !readDiscountAt ||\n        new Date(lastDiscount.createdAt) > new Date(readDiscountAt)\n      ) {\n        this.newDiscounts = true;\n      }\n    },\n    retryTask: function(task, hideNotice = false) {\n      chrome.runtime.sendMessage(\n        {\n          action: \"runTask\",\n          hideNotice: hideNotice,\n          taskId: task.id\n        },\n        function(response) {\n          if (!hideNotice) {\n            if (response.result == \"success\") {\n              weui.toast(\"手动运行成功\", 3000);\n            } else if (response.message) {\n              weui.alert(response.message, { title: \"任务暂未运行\" });\n            }\n          }\n        }\n      );\n    },\n    // 任务列表\n    getTaskList: function() {\n      this.taskList = getTasks();\n    },\n    // 处理登录状态\n    dealWithLoginState: function() {\n      let loginState = getLoginState();\n      this.loginState = loginState;\n\n      if (loginState.class == \"failed\") {\n        weui.dialog({\n          title: \"淘宝账号登录失效\",\n          content: `<p>账号登录失效后，签到任务将无法运行</p>`,\n          className: \"login-failed\",\n          buttons: [\n            {\n              label: \"去登录\",\n              type: \"primary\",\n              onClick: function() {\n                chrome.runtime.sendMessage(\n                  {\n                    action: \"openLogin\"\n                  },\n                  function(response) {\n                    console.log(\"Response: \", response);\n                  }\n                );\n              }\n            },\n            {\n              label: \"知道了\",\n              type: \"default\"\n            }\n          ]\n        });\n      }\n\n      function getStateDescription(loginState, type) {\n        return (\n          stateText[loginState[type].state] +\n          (loginState[type].message\n            ? `（ ${loginState[type].message} 上次检查： ${readableTime(\n                DateTime.fromISO(loginState[type].time)\n              )} ）`\n            : \"\")\n        );\n      }\n\n      this.loginStateDescription =\n        \"PC网页版登录\" +\n        getStateDescription(loginState, \"pc\") +\n        \"，移动网页版登录\" +\n        getStateDescription(loginState, \"m\");\n    },\n    renderMessages: function(messages) {\n      if (!messages) {\n        messages = getSetting(\"message\", []);\n        chrome.runtime.sendMessage({ action: \"getMessages\" });\n      }\n      this.messages = messages.map(function(message) {\n        if (message.type == \"coupon\") {\n          message.coupon = message.content;\n        }\n        message.time = readableTime(\n          message.timestamp\n            ? DateTime.fromMillis(message.timestamp)\n            : DateTime.fromISO(message.time)\n        );\n        return message;\n      });\n    },\n    showLoginState: function() {\n      $(\"#loginNotice\").show();\n    },\n    selectType: function(type) {\n      this.selectedTab = type;\n    },\n    readMessages: function() {\n      this.unreadCount = 0;\n      chrome.runtime.sendMessage(\n        {\n          text: \"clearUnread\"\n        },\n        function(response) {\n          console.log(\"Response: \", response);\n        }\n      );\n    },\n    readDiscounts: function() {\n      this.newDiscounts = false;\n    },\n    showChangelog: function() {\n      this.newChangelog = false;\n      localStorage.setItem(\"changelog_version\", this.currentVersion);\n      weui.dialog({\n        title: \"更新记录\",\n        content: `<iframe id=\"changelogIframe\" frameborder=\"0\" src=\"https://teaclub.zaoshu.so/changelog?buildId=${\n          process.env.BUILDID\n        }&browser=${\n          process.env.BROWSER\n        }&app=teaclub\" style=\"width: 100%;min-height: 350px;\"></iframe>`,\n        className: \"changelog\",\n        buttons: [\n          {\n            label: \"完成\",\n            type: \"primary\"\n          }\n        ]\n      });\n    }\n  }\n};\n</script>\n\n<style scoped>\n.weui-cells {\n  margin-top: 0;\n  overflow: unset;\n}\n\n.main-container {\n  display: flex;\n  justify-content: space-between;\n}\n.messages, .discounts {\n  overflow: hidden;\n  height: 515px;\n  background: #f9f9f9;\n}\n\n.message-items {\n  margin-top: 10px;\n  height: 504px;\n  overflow-y: auto;\n}\n.order-good.suspended {\n  opacity: 0.5;\n}\n</style>"
  },
  {
    "path": "src/components/discounts.vue",
    "content": "<template>\n  <div id=\"discounts\" class=\"contents-box discounts\">\n    <div class=\"top-bar\">\n      <div class=\"tabs\">\n        <ul class=\"tab-list\">\n          <li class=\"tabs-item\">\n            <a\n              :class=\"discountTab == 'featured' ? 'tabs-link is-active' : 'tabs-link'\"\n              @click=\"switchTab('featured')\"\n            >精选</a>\n          </li>\n          <li class=\"tabs-item\">\n            <a\n              :class=\"discountTab == 'concerned' ? 'tabs-link is-active' : 'tabs-link'\"\n              @click=\"switchTab('concerned')\"\n            >关注</a>\n          </li>\n          <li class=\"tabs-item\">\n            <a\n              :class=\"discountTab == 'hot' ? 'tabs-link is-active' : 'tabs-link'\"\n              @click=\"switchTab('hot')\"\n            >热榜</a>\n          </li>\n        </ul>\n      </div>\n      <div class=\"select-tag\" v-if=\"selectTag\">\n        <div class=\"tag-box\">\n          <span class=\"tag-name\">{{selectTag.name}}</span>\n        </div>\n        <button\n          v-if=\"followed\"\n          class=\"weui-btn weui-btn_mini weui-btn_disabled\"\n          @click=\"unfollowTag(selectTag)\"\n        >取消</button>\n        <button\n          v-else\n          class=\"weui-btn weui-btn_mini weui-btn_primary\"\n          @click=\"followTag(selectTag)\"\n        >关注</button>\n      </div>\n      <div class=\"search\" v-else>\n        <input v-model=\"keyword\" placeholder=\"输入关键词搜索\" v-on:keyup.enter=\"search\">\n        <i v-if=\"showClear\" class=\"circle-close\" @click=\"clear\">&times;</i>\n      </div>\n    </div>\n    <div class=\"weui-cells discount-list\" v-if=\"discountList\">\n      <events :events=\"events\" v-if=\"discountTab == 'featured' && events && events.length > 0\"></events>\n      <div class=\"discounts-box\" v-for=\"discount in discountList\" :key=\"discount.id\">\n        <div :class=\"discount.pinned ? 'discount pinned' : 'discount'\">\n          <div class=\"title\" @mouseover=\"discount.focus = true\" @mouseout=\"discount.focus = false\">\n            <span class=\"merchant\" v-if=\"discount.merchant\">\n              <img v-lazy=\"discount.merchant.icon\" :alt=\"discount.merchant.name\">\n            </span>\n            <a :href=\"`${discount.goodLink}`\" target=\"_blank\">{{discount.title}}</a>\n            <span class=\"discount_price\">{{discount.price}}</span>\n            <report :discount=\"discount\"></report>\n          </div>\n          <div class=\"description\">\n            <a :href=\"`${discount.goodLink}`\" target=\"_blank\">\n              <img\n                v-if=\"discount.photo\"\n                v-lazy=\"`${discount.photo}`\"\n                @error.once=\"backup_picture($event)\"\n                width=\"75\"\n                class=\"discount-photo backup_picture\"\n                :alt=\"discount.title\"\n              >\n            </a>\n            <p>{{discount.description}}</p>\n          </div>\n          <div class=\"tags\">\n            <span\n              class=\"tag\"\n              v-for=\"tag in discount.tags\"\n              :key=\"tag.id\"\n              @click=\"filterByTag(tag)\"\n            >{{tag.name}}</span>\n          </div>\n          <div class=\"weui-cell__ft\">\n            <span class=\"time\">{{discount.displayTime}}</span>\n            <a\n              v-if=\"discount.couponLink\"\n              class=\"get-coupon\"\n              data-microtip-position=\"top\" role=\"tooltip\"\n              :aria-label=\"discount.couponName\"\n              :href=\"`${discount.couponLink}`\"\n              target=\"_blank\"\n            >优惠券</a>\n            <a class=\"go-buy\" :href=\"`${discount.goodLink}`\" target=\"_blank\">去购买</a>\n          </div>\n        </div>\n      </div>\n      <infinite-loading v-if=\"discountList.length > 20\" spinner=\"waveDots\" @infinite=\"infiniteHandler\">\n        <div slot=\"no-more\" class=\"no-more\">😭暂时没有近期优惠了</div>\n        <div slot=\"no-results\" class=\"no-results\">😭没有更多优惠信息了</div>\n      </infinite-loading>\n      <div v-if=\"discountTab == 'concerned' && followedTagIds.length < 1\" class=\"no_message\">\n        <h4>暂时还没有关注任何标签</h4>\n        <p class=\"tips\">点击优惠信息中的标签可以筛选并关注标签哦</p>\n      </div>\n      <div v-if=\"keyword && discountList.length < 1\" class=\"no_message\">\n        <h4>没有找到任何优惠</h4>\n        <p class=\"tips\">为了保证结果有效性，只展示近两周的优惠</p>\n      </div>\n      <div v-if=\"discountList.length > 0\" class=\"self-recommendation\">\n        <p class=\"tips\">商家自荐/优惠爆料可联系微信：cindywchat</p>\n      </div>\n    </div>\n    <div class=\"loading\" v-else>\n      <loading></loading>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { DateTime } from \"luxon\";\nimport InfiniteLoading from 'vue-infinite-loading';\nimport { getSetting, readableTime } from \"../utils\";\nimport loading from \"./loading.vue\";\nimport report from \"./report.vue\";\nimport events from \"./events.vue\";\n\nexport default {\n  name: \"discounts\",\n  components: { loading, report, events, InfiniteLoading },\n  data() {\n    return {\n      followedTagIds: getSetting(\"followedTagIds\", []),\n      discountTab: \"featured\",\n      discountList: null,\n      selectTag: null,\n      page: 1,\n      keyword: null\n    };\n  },\n  mounted: async function() {\n    this.getDiscounts();\n  },\n  computed: {\n    followed: function() {\n      return (\n        this.selectTag &&\n        this.followedTagIds.length > 0 &&\n        this.followedTagIds.indexOf(this.selectTag.id) > -1\n      );\n    },\n    showClear: function() {\n      return this.keyword && this.keyword.length > 0;\n    },\n    events: function() {\n      return this.discountList ? this.discountList.filter(discount => discount.event) : [];\n    },\n    condition: function() {\n      if (this.keyword) {\n        return {\n          keyword: this.keyword\n        }\n      }\n      switch (this.discountTab) {\n        case \"featured\":\n          return {\n            all: false\n          };\n          break;\n        case \"concerned\":\n          if (this.followedTagIds.length > 0) {\n            return {\n              tagIds: this.followedTagIds.join(\",\")\n            };\n          } else {\n            return {};\n          }\n          break;\n        case \"hot\":\n          return {\n            hot: true\n          };\n          break;\n        default:\n          break;\n      }\n      return {}\n    }\n  },\n  methods: {\n    backup_picture: function(e) {\n      e.currentTarget.src = \"https://jjbcdn.zaoshu.so/web/img_error.png\";\n    },\n    loadDiscountFormApi: async function(params) {\n      let queryParams = new URLSearchParams(params);\n      let response = await fetch(\n        `https://teaclub.zaoshu.so/discount?${queryParams.toString()}`\n      );\n      return await response.json();\n    },\n    getDiscounts: async function(condition) {\n      this.discountList = null;\n      this.selectTag = null;\n      this.page = 1\n      const discounts = await this.loadDiscountFormApi(condition)\n      this.discountList = discounts.map(function(discount) {\n        discount.displayTime = readableTime(\n          DateTime.fromISO(discount.createdAt)\n        );\n        discount.focus = false\n        return discount;\n      });\n      localStorage.setItem(\"readDiscountAt\", new Date());\n      this.$forceUpdate();\n    },\n    infiniteHandler: async function ($state) {\n      if (this.selectTag) return $state.complete();\n      const discounts = await this.loadDiscountFormApi(Object.assign({}, this.condition, {\n        page: this.page,\n      }))\n      if (discounts.length) {\n        this.page += 1;\n        this.discountList.push(...discounts.map(function(discount) {\n          discount.displayTime = readableTime(\n            DateTime.fromISO(discount.createdAt)\n          );\n          discount.focus = false\n          return discount;\n        }));\n        $state.loaded();\n      } else {\n        $state.complete();\n      }\n    },\n    search: async function() {\n      this.getDiscounts(this.condition);\n    },\n    clear: async function() {\n      this.keyword = null;\n      this.switchTab(\"featured\");\n    },\n    filterByTag: async function(tag) {\n      this.discountTab = null;\n      this.discountList = null;\n      this.page = 1\n      let response = await fetch(\n        `https://teaclub.zaoshu.so/discount/tag/${tag.id}`\n      );\n      let data = await response.json();\n      this.selectTag = data.tag;\n      this.discountList = data.discounts.map(function(discount) {\n        discount.displayTime = readableTime(\n          DateTime.fromISO(discount.createdAt)\n        );\n        discount.focus = false\n        return discount;\n      });\n      this.$forceUpdate();\n    },\n    unfollowTag: async function(tag) {\n      this.followedTagIds = this.followedTagIds.filter(\n        tagId => tagId != tag.id\n      );\n      localStorage.setItem(\n        \"followedTagIds\",\n        JSON.stringify(this.followedTagIds)\n      );\n    },\n    followTag: async function(tag) {\n      let followedTagIds = this.followedTagIds;\n      followedTagIds.push(tag.id);\n      localStorage.setItem(\n        \"followedTagIds\",\n        JSON.stringify(this.followedTagIds)\n      );\n    },\n    switchTab: async function(type) {\n      this.discountTab = type;\n      this.selectTag = null;\n      if (type == \"concerned\" && this.followedTagIds.length < 1) {\n        this.discountList = []\n      } else {\n        this.getDiscounts(this.condition);\n      }\n    }\n  }\n};\n</script>\n\n<style scoped>\n.tabs {\n  height: 40px;\n  width: 200px;\n  float: left;\n}\n\n.tab-list {\n  margin-bottom: 0;\n  box-shadow: none;\n  padding-top: 0.2em;\n  padding-left: 0.2em;\n}\n\n.tab-list li {\n  list-style-type: none;\n}\n\n.tab-list .tabs-item {\n  display: inline-block;\n  padding: 0 15px;\n}\n\n.tab-list .tabs-link {\n  position: relative;\n  display: inline-block;\n  padding: 12px 0;\n  font-size: 16px;\n  line-height: 22px;\n  text-align: center;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n.tabs-link.is-active {\n  font-weight: 600;\n  color: #921714;\n}\n\n.tabs-link.is-active::after {\n  position: absolute;\n  right: 0;\n  bottom: -1px;\n  left: 0;\n  height: 3px;\n  background: #921714;\n  content: \"\";\n}\n.tags {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  width: 55%;\n  height: 26px;\n  display: -webkit-box;\n  max-height: 26px;\n  -webkit-line-clamp: 1;\n}\n.tag {\n  font-size: 12px;\n  color: #696969;\n  cursor: pointer;\n  padding: 0.5em;\n  margin-right: 0.5em;\n}\n\n.top-bar {\n  height: 47px;\n  border-bottom: 1px solid #eeeeee3d;\n  z-index: 10;\n  display: flex;\n  justify-content: space-between;\n}\n\n.select-tag,\n.search {\n  line-height: 50px;\n  font-size: 14px;\n  text-align: right;\n  position: relative;\n}\n\n.search input {\n  -webkit-appearance: none;\n  border-radius: 4px;\n  border: 1px solid #0000003d;\n  box-sizing: border-box;\n  color: #606266;\n  display: inline-block;\n  font-size: inherit;\n  height: 30px;\n  line-height: 30px;\n  outline: none;\n  padding: 0 15px;\n  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);\n  margin-top: 12px;\n  margin-right: 6px;\n  background: #eeeeee42;\n}\n\n.search input:focus {\n  outline: none;\n  border-color: #409eff;\n}\n\n.search .circle-close {\n  position: absolute;\n  right: 12px;\n  color: #ccc;\n  padding: 0 2px;\n  cursor: pointer;\n}\n\n.tag-box {\n  width: 140px;\n  text-align: right;\n  float: left;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: -webkit-box;\n  -webkit-line-clamp: 1;\n  -moz-box-orient: vertical;\n  -webkit-box-orient: vertical;\n}\n\n.select-tag span.tag-name {\n  font-size: 14px;\n  padding: 6px 8px;\n  vertical-align: middle;\n  color: #737373;\n  height: 30px;\n  line-height: 30px;\n  margin-top: 11px;\n}\n\n.select-tag a.weui-btn {\n  vertical-align: middle;\n  margin-right: 1em;\n  padding-right: 16px;\n}\n.discount-list {\n  overflow-y: auto;\n  height: 465px;\n  margin-top: 0;\n}\n\n.discount-list li {\n  display: block;\n}\n\n.discount-list h5 {\n  padding: 0.2em 1em;\n}\n\n.discount {\n  border-bottom: 1px solid #eeeeee3d;\n  padding: 12px 8px;\n  position: relative;\n}\n\n.discount.pinned {\n  background-color: #fff7e0;\n}\n\n.discount .title {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: -webkit-box;\n  max-height: 36px;\n  -webkit-line-clamp: 2;\n  -moz-box-orient: vertical;\n  -webkit-box-orient: vertical;\n  padding: 5px;\n  line-height: 22px;\n  font-size: 15px;\n  font-weight: 500;\n}\n.discount .description {\n  display: flex;\n  padding: 15px 5px;\n}\n\n.discount .discount-photo {\n  width: 75px;\n  height: 75px;\n}\n\n.discount_price {\n  color: #f04848;\n  font-weight: bold;\n}\n\n.discount .time {\n  font-size: 12px;\n}\n\n.discount .description p {\n  display: inline-block;\n  font-size: 13px;\n  color: #666;\n  width: 340px;\n  padding-left: 10px;\n  max-height: 75px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: -webkit-box;\n  -webkit-line-clamp: 2;\n  -moz-box-orient: vertical;\n  -webkit-box-orient: vertical;\n  line-height: 1.5;\n}\n\n.discount .merchant {\n  height: 15px;\n  line-height: 15px;\n  display: inline-block;\n  vertical-align: middle;\n  margin-top: -4px;\n}\n\n.discount .get-coupon {\n  font-size: 12px;\n  padding: 0.3em;\n}\n\n.discount .weui-cell__ft {\n  margin-top: -25px;\n  padding-bottom: 5px;\n}\n\n.discount a.go-buy:hover {\n  color: #fff;\n  background: #149813;\n}\n\n.weui-cells:after{\n  border-bottom: none\n}\n\n.no-more, .no-results{\n    color: #666;\n    padding: 3px;\n    font-size: 14px;\n}\n</style>"
  },
  {
    "path": "src/components/events.vue",
    "content": "<template>\n  <hooper>\n    <slide v-for=\"(event, index)  in events\" :key=\"event.id\" :index=\"index\">\n      <a :href=\"`${event.goodLink}`\" target=\"_blank\">\n        <img :src=\"event.banner || event.photo\" :title=\"event.title\" height=\"120\"/>\n        <p class=\"title\">{{event.title}}</p>\n      </a>\n    </slide>\n\n    <hooper-navigation slot=\"hooper-addons\"></hooper-navigation>\n  </hooper>\n</template>\n<script>\nimport { Hooper, Slide, Navigation as HooperNavigation } from 'hooper';\nimport 'hooper/dist/hooper.css';\n\nimport weui from \"weui.js\";\nexport default {\n  name: \"report\",\n  props: [\"events\"],\n    components: {\n    Hooper,\n    Slide,\n    HooperNavigation\n  },\n  data() {\n    return {\n      show: false\n    };\n  },\n  methods: {}\n};\n</script>\n<style scoped>\n.hooper{\n  height: 120px;\n}\n.hooper li{\n  text-align: center;\n}\n.hooper p.title{\n      position: absolute;\n    bottom: 0px;\n    text-align: center;\n    width: 100%;\n    color: #fff;\n    background-color: #33333352;\n    font-size: 14px;\n    padding: 3px 6px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    -webkit-line-clamp: 1;\n    -webkit-box-orient: vertical;\n    display: -webkit-box;\n}\n</style>\n"
  },
  {
    "path": "src/components/guide.vue",
    "content": "<template>\n  <div class=\"guide\">\n    <div class=\"js_dialog\" style=\"opacity: 1;\" v-if=\"step > 0\">\n      <div class=\"weui-mask\"></div>\n      <div class=\"weui-dialog\">\n        <div class=\"testbox step-1\" v-if=\"step == 1\">\n          <div class=\"weui-dialog__hd\">\n            <strong class=\"weui-dialog__title\">恭喜安装成功！</strong>\n          </div>\n          <div class=\"weui-dialog__bd\">\n            <p>感谢你使用茶友会！以下是一些简单的介绍：</p>\n            <p>茶友会是一个<a href=\"https://github.com/sunoj/teaclub\" target=\"_blank\">公开源代码</a>的浏览器插件。它能自动搜索淘宝商品的渠道优惠券，还内置一系列替你进行签到、领飞猪里程的小任务。</p>\n            <p>由于淘宝网页经常更新，茶友会受其影响可能部分功能有时变得不可用，因此茶友会会经常更新以保持功能正常。如果你使用的不是\n              <a v-if=\"browser == 'chrome'\" href=\"https://chrome.google.com/webstore/detail/igedhbjllcmgidlmhclmphmhlllkibkb\" target=\"_blank\">Chrome 拓展商店</a>\n              <a v-if=\"browser == 'firefox'\" href=\"https://addons.mozilla.org/zh-CN/firefox/addon/teaclub/\" target=\"_blank\">Firefox 官方商店</a>\n              <a v-if=\"browser == 'edge'\" href=\"https://microsoftedge.microsoft.com/addons/detail/aniokofeapdnfnihjgfeonlkmhfajobk\" target=\"_blank\">Microsoft Edge 扩展中心</a>\n             安装，强烈建议您使用上述渠道安装，只有这样你才能获得官方的自动更新。</p>\n          </div>\n          <div class=\"weui-dialog__ft\">\n            <a class=\"weui-dialog__btn weui-dialog__btn_primary answer\" @click=\"step = 2\">继续</a>\n          </div>\n        </div>\n        <div class=\"testbox step-2\" v-if=\"step == 2\">\n          <div class=\"weui-dialog__hd\">\n            <strong class=\"weui-dialog__title\">登录账号</strong>\n          </div>\n          <div class=\"weui-dialog__bd\">\n            <p>如你所知，茶友会是一个浏览器插件。在你的授权下，茶友会代替你自动访问淘宝的网页来执行一系列操作。 </p>\n            <p>很显然，\n              <b>茶友会需要您登录淘宝才能完成你指定的工作</b>。由于淘宝的登录有效期较短，你通常需要每天登录一次淘宝账号才能保证签到任务自动运行。\n            </p>\n            <p>当登录失效时，茶友会将会提醒你。除非你重新登录，否则茶友会将无法继续完成任何工作。</p>\n          </div>\n          <div class=\"weui-dialog__ft\">\n            <a v-if=\"loginState.class == 'unknown'\" :class=\"`weui-dialog__btn weui-dialog__btn_primary answer`\" @click=\"done('openLogin')\">现在登录</a>\n            <a class=\"weui-dialog__btn weui-dialog__btn_primary answer\" @click=\"done\">知道了</a>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { getSetting } from \"../utils\";\n\nexport default {\n  name: \"guide\",\n  props: [\"loginState\"],\n  data() {\n    return {\n      step: 1,\n      browser: process.env.BROWSER\n    };\n  },\n  methods: {\n    done: async function(action) {\n      this.step = 0\n      localStorage.setItem('showGuideAt', new Date())\n      if (action == 'openLogin') {\n        chrome.runtime.sendMessage({\n          action: \"openLogin\",\n        }, function(response) {\n          console.log(\"Response: \", response);\n        });\n      }\n    }\n  }\n};\n</script>\n"
  },
  {
    "path": "src/components/links.vue",
    "content": "<template>\n  <div class=\"links\">\n    <span :class=\"action.class\" v-for=\"(action, index)  in actionLinks\" :key=\"action.id\" :index=\"index\" :style=\"action.style\">\n      <a\n        v-if=\"action.type == 'dialog'\"\n        href=\"#\"\n        data-microtip-position=\"top\" role=\"tooltip\"\n        :aria-label=\"action.description\"\n        :style=\"action.linkStyle\"\n        :class=\"action.linkClass\"\n        @click=\"showDialog(action)\"\n      >{{action.title}}\n      </a>\n      <a\n        v-if=\"action.type == 'link'\"\n        data-microtip-position=\"top\" role=\"tooltip\"\n        :aria-label=\"action.description\"\n        :href=\"action.url\"\n        :style=\"action.linkStyle\"\n        :class=\"action.linkClass\"\n        target=\"_blank\"\n      >{{action.title}}</a>\n    </span>\n  </div>\n</template>\n\n<script>\nimport weui from \"weui.js\";\nimport { getSetting } from \"../utils\";\nexport default {\n  name: \"links\",\n  data() {\n    return {\n      actionLinks: getSetting(\"teaclub:action-links\", [\n        {\n          type: \"dialog\",\n          class: \"el-tag el-tag--warning\",\n          linkClass: \"tippy\",\n          title: \"活动推荐\",\n          description: \"热门的促销活动推荐\",\n          style: \"margin-right: 3px;\",\n          mode: \"iframe\",\n          url: \"https://jjb.zaoshu.so/recommend\"\n        },\n        {\n          type: \"dialog\",\n          \"mode\": \"image\",\n          class: \"el-tag el-tag--danger\",\n          linkClass: \"tippy\",\n          title: \"支付宝红包\",\n          description: \"天天领支付宝红包\",\n          url: \"https://jjbcdn.zaoshu.so/chrome/alipayred.png\"\n        }\n      ])\n    };\n  },\n  methods: {\n    showDialog: async function(action) {\n      let content = \"\"\n      if (action.mode == \"iframe\") {\n        content = `\n          <iframe frameborder=\"0\" src=\"${action.url}\" style=\"width: 100%;min-height: 420px;min-width: 400px;\"></iframe>\n        `\n      }\n      if (action.mode == \"image\") {\n        content = `\n          <img src=\"${action.url}\" style=\"width: 270px;\"></img>\n        `\n      }\n      weui.dialog({\n        title: action.title,\n        content: content,\n        className: \"dialog\",\n        buttons: [\n          {\n            label: \"完成\",\n            type: \"primary\"\n          }\n        ]\n      });\n    },\n  }\n};\n</script>\n\n<style scoped>\n  .links {\n    display: inline-block;\n  }\n</style>"
  },
  {
    "path": "src/components/loading.vue",
    "content": "<template>\n  <div class=\"loading-masker\">\n    <div class=\"white-widget grey-bg author-area\" v-for=\"(masker, index) in numbers\" :key=\"index\">\n      <div class=\"auth-info row\">\n        <div class=\"timeline-wrapper\">\n          <div class=\"timeline-item\">\n            <div class=\"animated-background\">\n              <div class=\"background-masker header-top\"></div>\n              <div class=\"background-masker header-left\"></div>\n              <div class=\"background-masker header-right\"></div>\n              <div class=\"background-masker header-bottom\"></div>\n              <div class=\"background-masker subheader-left\"></div>\n              <div class=\"background-masker subheader-right\"></div>\n              <div class=\"background-masker subheader-bottom\"></div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: \"loading\",\n  data() {\n    return {\n      numbers: [1, 2, 3, 4, 5]\n    };\n  }\n};\n</script>\n<style scoped>\n.timeline-item {\n  background: #ffffff03;\n  border-bottom: 1px solid #f2f2f22e;\n  padding: 25px;\n  margin: 0 auto;\n}\n@keyframes placeHolderShimmer {\n  0% {\n    background-position: -468px 0;\n  }\n  100% {\n    background-position: 468px 0;\n  }\n}\n.animated-background {\n  animation-duration: 1s;\n  animation-fill-mode: forwards;\n  animation-iteration-count: infinite;\n  animation-name: placeHolderShimmer;\n  animation-timing-function: linear;\n  background: #f6f7f8;\n  background: linear-gradient(to right, #eeeeee42 8%, #dddddd61 18%, #eeeeee3d 33%);\n  background-size: 800px 104px;\n  height: 40px;\n  position: relative;\n}\n.background-masker {\n      background: #ffffff17;\n  position: absolute;\n}\n/* Every thing below this is just positioning */\n.background-masker.header-top,\n.background-masker.header-bottom,\n.background-masker.subheader-bottom {\n  top: 0;\n  left: 40px;\n  right: 0;\n  height: 10px;\n}\n.background-masker.header-left,\n.background-masker.subheader-left,\n.background-masker.header-right,\n.background-masker.subheader-right {\n  top: 10px;\n  left: 40px;\n  height: 8px;\n  width: 10px;\n}\n.background-masker.header-bottom {\n  top: 18px;\n  height: 6px;\n}\n.background-masker.subheader-left,\n.background-masker.subheader-right {\n  top: 24px;\n  height: 6px;\n}\n.background-masker.header-right,\n.background-masker.subheader-right {\n  width: auto;\n  left: 300px;\n  right: 0;\n}\n.background-masker.subheader-right {\n  left: 230px;\n}\n.background-masker.subheader-bottom {\n  top: 30px;\n  height: 10px;\n}\n.background-masker.content-top,\n.background-masker.content-second-line,\n.background-masker.content-third-line,\n.background-masker.content-second-end,\n.background-masker.content-third-end,\n.background-masker.content-first-end {\n  top: 40px;\n  left: 0;\n  right: 0;\n  height: 6px;\n}\n.background-masker.content-top {\n  height: 20px;\n}\n.background-masker.content-first-end,\n.background-masker.content-second-end,\n.background-masker.content-third-end {\n  width: auto;\n  left: 380px;\n  right: 0;\n  top: 60px;\n  height: 8px;\n}\n.background-masker.content-second-line {\n  top: 68px;\n}\n.background-masker.content-second-end {\n  left: 420px;\n  top: 74px;\n}\n.background-masker.content-third-line {\n  top: 82px;\n}\n.background-masker.content-third-end {\n  left: 300px;\n  top: 88px;\n}\n</style>\n"
  },
  {
    "path": "src/components/popup.vue",
    "content": "<template>\n  <div v-if=\"events && events.length > 0 && loadEvents\">\n    <div class=\"popup-show\" v-if=\"showPopup\">\n      <div class=\"js_dialog\" style=\"opacity: 1;\">\n        <div class=\"weui-mask\"></div>\n        <hooper>\n          <slide class=\"slide\" v-for=\"(event, index) in events\" :key=\"event.id\" :index=\"index\">\n            <a :href=\"`${event.link}`\" target=\"_blank\">\n              <img :src=\"event.poster\" :title=\"event.title\" width=\"300\"/>\n            </a>\n          </slide>\n          <hooper-navigation slot=\"hooper-addons\"></hooper-navigation>\n        </hooper>\n        <div class=\"close-popup\" @click=\"close\">&times;</div>\n      </div>\n    </div>\n    <div class=\"preload\">\n      <img :src=\"events[0].poster\" width=\"300\"/>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { DateTime } from 'luxon'\nimport { getSetting, saveSetting } from \"../utils\";\nimport { Hooper, Slide, Navigation as HooperNavigation } from 'hooper';\nimport 'hooper/dist/hooper.css';\n\nconst today = DateTime.local().toFormat(\"o\")\n\nexport default {\n  name: \"popup\",\n  components: {\n    Hooper,\n    Slide,\n    HooperNavigation\n  },\n  data() {\n    return {\n      showPopup: false,\n      loadEvents: false,\n      events: getSetting('events', []),\n      usage: getSetting(`temporary:usage-popup_d:${today}`, 0),\n      display: getSetting(\"displayPopup\", {\n        \"percentage\": 0,\n        \"limit\": 0\n      }),\n    };\n  },\n  mounted: async function() {\n    setTimeout(() => {\n      this.preload()\n    }, 200);\n    setTimeout(() => {\n      this.show()\n    }, 400);\n  },\n  methods: {\n    close: async function() {\n      this.showPopup = false\n      this.$emit('close')\n    },\n    preload: function () {\n      let events = this.getEvents()\n      if (events && events.length > 0 && this.display.limit > 0) {\n        this.loadEvents = true\n      }\n    },\n    show: function () {\n      if (this.loadEvents && this.usage < this.display.limit && this.display.percentage > Math.floor(Math.random() * 100) + 1) {\n        if ($(\".js_dialog:visible\").length < 1) {\n          this.showPopup = true\n          saveSetting(`temporary:usage-popup_d:${today}`, this.usage + 1)\n        }\n      }\n    },\n    getEvents: function() {\n      let events = getSetting(\"events\", []);\n      events = events.filter(event => {\n        const isValid = event.validUntil ? DateTime.fromJSDate(new Date(event.validUntil)) > DateTime.local() : true\n        const isStarted = event.startAt ? DateTime.fromJSDate(new Date(event.startAt)) < DateTime.local() : true\n        return isValid && isStarted\n      });\n      this.events = events\n      return events;\n    },\n  }\n};\n</script>\n<style scoped>\n.popup-show{\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  top: 0;\n  left: 0;\n}\n.preload{\n  display: none;\n}\n.popup-show .js_dialog{\n  height: 100%;\n  display: flex;\n  align-items: center;\n  flex-direction: column;\n  justify-content: center;\n}\n.hooper{\n  max-height: 80%;\n  min-height: 450px;\n  z-index: 1001;\n  width: 400px;\n}\n.hooper .slide {\n  text-align: center;\n}\n.close-popup{\n  border: 2px solid #cacaca;\n  border-radius: 20px;\n  width: 30px;\n  height: 30px;\n  z-index: 1002;\n  text-align: center;\n  font-size: 29px;\n  line-height: 26px;\n  color: #e8e8e8;\n  font-weight: 100;\n  margin-top: -20px;\n  cursor: pointer;\n}\n</style>"
  },
  {
    "path": "src/components/report.vue",
    "content": "<template>\n<div class=\"reprot\">\n  <div class=\"report-mask\" @click=\"hide\" v-if=\"show\"></div>\n  <div class=\"report-problem\" :ref=\"`report-${discount.id}`\">\n    <div class=\"report-icon\" data-microtip-position=\"bottom-right\" role=\"tooltip\" aria-label=\"反馈问题\" @click=\"showList\">\n      <span>i</span>\n    </div>\n    <div :class=\"`weui-cells weui-cells_radio ${leftList ? 'turn-left': ''}`\" v-if=\"show\">\n      <label class=\"weui-cell weui-check__label\" :for=\"`report-${discount.id}_expired`\">\n        <div class=\"weui-cell__bd\">\n          <p>优惠失效</p>\n        </div>\n        <div class=\"weui-cell__ft\">\n          <input\n            type=\"radio\"\n            v-model=\"code\"\n            value=\"expired\"\n            class=\"weui-check\"\n            :name=\"`report-${discount.id}`\"\n            :id=\"`report-${discount.id}_expired`\"\n          >\n          <span class=\"weui-icon-checked\"></span>\n        </div>\n      </label>\n      <label class=\"weui-cell weui-check__label\" :for=\"`report-${discount.id}_soldout`\">\n        <div class=\"weui-cell__bd\">\n          <p>告罄缺货</p>\n        </div>\n        <div class=\"weui-cell__ft\">\n          <input\n            type=\"radio\"\n            v-model=\"code\"\n            value=\"soldout\"\n            :name=\"`report-${discount.id}`\"\n            class=\"weui-check\"\n            :id=\"`report-${discount.id}_soldout`\"\n          >\n          <span class=\"weui-icon-checked\"></span>\n        </div>\n      </label>\n      <label class=\"weui-cell weui-check__label\" :for=\"`report-${discount.id}_wronglink`\">\n        <div class=\"weui-cell__bd\">\n          <p>链接错误</p>\n        </div>\n        <div class=\"weui-cell__ft\">\n          <input\n            type=\"radio\"\n            v-model=\"code\"\n            :name=\"`report-${discount.id}`\"\n            value=\"wronglink\"\n            class=\"weui-check\"\n            :id=\"`report-${discount.id}_wronglink`\"\n          >\n          <span class=\"weui-icon-checked\"></span>\n        </div>\n      </label>\n      <button class=\"report-btn\" @click=\"sendReport\" v-show=\"code\">{{ loading ? `发送中..`: `发送反馈`}}</button>\n    </div>\n</div>\n  </div>\n</template>\n\n<script>\nimport weui from \"weui.js\";\nexport default {\n  name: \"report\",\n  props: [\"discount\"],\n  data() {\n    return {\n      code: null,\n      show: false,\n      leftList: false\n    };\n  },\n  methods: {\n    showList: async function() {\n      this.show = !this.show;\n      if (this.$refs[`report-${this.discount.id}`].offsetLeft > 300) {\n        this.leftList = true;\n      }\n    },\n    hide: async function() {\n      this.show = false\n    },\n    sendReport: async function() {\n      this.loading = true\n      try {\n        let response = await fetch(\n        `https://jjb.zaoshu.so/discount/${this.discount.id}`,\n        {\n          body: JSON.stringify({\n            code: this.code\n          }),\n          cache: 'no-cache',\n          headers: {\n            \"content-type\": \"application/json\"\n          },\n          method: \"POST\",\n          mode: \"cors\",\n          redirect: \"follow\"\n        }\n      );\n      let result = await response.json();\n      } catch (error) {\n        console.error(error)\n      }\n      this.loading = false\n      this.show = false\n      weui.toast(\"感谢反馈\", 500);\n    }\n  }\n};\n</script>\n<style scoped>\n\n.reprot{\n  display: inline;\n}\n\n.report-icon {\n  display: none\n}\n\n.discounts-box:hover .report-icon {\n  display: inline-block\n}\n\n.discounts-box:hover .report-problem{\n  display: inline;\n}\n\n.report-mask {\n      width: 100%;\n    height: 100%;\n    display: block;\n    position: absolute;\n    top: 0;\n    left: 0;\n}\n\n.report-problem {\n  z-index: 5;\n  position: absolute;\n  display: none;\n  margin-left: 5px;\n}\n\n.report-problem .weui-cells {\n  width: 160px;\n  margin-top: 5px;\n  border: 1px solid #e0e0e0;\n  border-top: 0;\n  border-bottom: 0;\n  font-size: 14px;\n  font-weight: normal;\n}\n\n.report-problem .weui-cells.turn-left {\n  margin-left: -120px;\n}\n\n.report-btn {\n  width: 100%;\n  background: #fdf295;\n  height: 32px;\n  border: 0;\n  cursor: pointer;\n}\n\n.report-icon span {\n  border: 1px solid #ccc;\n  width: 16px;\n  height: 16px;\n  border-radius: 10px;\n  display: block;\n  text-align: center;\n  font-size: 14px;\n  color: #3a3a3a;\n  margin-top: 3px;\n  line-height: 17px;\n  cursor: pointer;\n  background: #eee;\n  font-family: monospace;\n}\n</style>\n"
  },
  {
    "path": "src/content_script.js",
    "content": "import 'weui';\nimport weui from 'weui.js';\nimport QRCode from \"qrcode-svg\";\n\nimport '../static/style/style.css'\n\nvar observeDOM = (function () {\n  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver\n  return function (obj, callback) {\n    // define a new observer\n    var obs = new MutationObserver(function (mutations, observer) {\n      if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) {\n        callback(observer);\n      }\n    });\n    // have the observer observe foo for changes in children\n    obs.observe(obj, { childList: true, subtree: true });\n  };\n})();\n\nObject.defineProperty(Array.prototype, 'chunk', {\n  value: function (chunkSize) {\n    var array = this;\n    return [].concat.apply([],\n      array.map(function (elem, i) {\n        return i % chunkSize ? [] : [array.slice(i, i + chunkSize)];\n      })\n    );\n  }\n});\n\n\nfunction mockTap(element) {\n  let rect = element.getBoundingClientRect()\n  sendTouchEvent(rect.x + 3, rect.y + 3, element, 'touchstart');\n  sendTouchEvent(rect.x + 3, rect.y + 3, element, 'touchend');\n}\n\n// 模拟点击 (原生)\nfunction simulateClick(domNode, mouseEvent) {\n  if (mouseEvent && domNode) {\n    return mockClick(domNode)\n  }\n  try {\n    mockTap(domNode)\n    mockClick(domNode)\n  } catch (error) {\n    console.log('fullback to mockClick', error)\n    mockClick(domNode)\n  }\n}\n\nfunction mockClick(element) {\n  var dispatchMouseEvent = function (target, var_args) {\n    var e = document.createEvent(\"MouseEvents\");\n    e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));\n    target.dispatchEvent(e);\n  };\n  if (element) {\n    dispatchMouseEvent(element, 'mouseover', true, true);\n    dispatchMouseEvent(element, 'mousedown', true, true);\n    dispatchMouseEvent(element, 'click', true, true);\n    dispatchMouseEvent(element, 'mouseup', true, true);\n  }\n}\n\n/* eventType is 'touchstart', 'touchmove', 'touchend'... */\nfunction sendTouchEvent(x, y, element, eventType) {\n  if ('TouchEvent' in window && TouchEvent.length > 0) {\n    const touchObj = new Touch({\n      identifier: Date.now(),\n      target: element,\n      clientX: x,\n      clientY: y,\n      radiusX: 2.5,\n      radiusY: 2.5,\n      rotationAngle: 10,\n      force: 0.5,\n    });\n    const touchEvent = new TouchEvent(eventType, {\n      cancelable: true,\n      bubbles: true,\n      touches: [touchObj],\n      targetTouches: [],\n      changedTouches: [touchObj],\n      shiftKey: true,\n    });\n    element.dispatchEvent(touchEvent);\n  } else {\n    console.log('no TouchEvent')\n  }\n}\n\nfunction injectScript(file, node) {\n  var th = document.getElementsByTagName(node)[0];\n  var s = document.createElement('script');\n  s.setAttribute('type', 'text/javascript');\n  s.setAttribute('charset', \"UTF-8\");\n  s.setAttribute('src', file);\n  th.appendChild(s);\n}\n\nfunction injectScriptCode(code, node = 'body') {\n  var th = document.getElementsByTagName(node)[0];\n  var script = document.createElement('script');\n  script.setAttribute('type', 'text/javascript');\n  script.setAttribute('language', 'JavaScript');\n  script.textContent = code;\n  th.appendChild(script);\n}\n\ninjectScriptCode(`\n  if (typeof hrl != 'undefined' && typeof host != 'undefined') {\n    document.write('<a style=\"display:none\" href=\"' + hrl + '\" id=\"exe\"></a>');\n    document.getElementById('exe').click()\n  }\n`, 'body')\n\nfunction escapeSpecialChars(jsonString) {\n  return jsonString.replace(/\\\\n/g, \"\\\\n\").replace(/\\\\'/g, \"\\\\'\").replace(/\\\\\"/g, '\\\\\"').replace(/\\\\&/g, \"\\\\&\").replace(/\\\\r/g, \"\\\\r\").replace(/\\\\t/g, \"\\\\t\").replace(/\\\\b/g, \"\\\\b\").replace(/\\\\f/g, \"\\\\f\");\n}\n\nvar pageTaskRunning = false\n\n// 获取设置\nfunction getSetting(name, cb) {\n  chrome.runtime.sendMessage({\n    text: \"getSetting\",\n    content: name\n  }, function (response) {\n    cb(response)\n    console.log(\"getSetting Response: \", name, response);\n  });\n}\n\nfunction createElementFromHTML(htmlString) {\n  var div = document.createElement('div');\n  div.innerHTML = htmlString.trim();\n  return div.firstChild;\n}\n\n\nfunction addDiscountElement() {\n  var newDiv = createElementFromHTML(`\n    <div id=\"teaclub\">\n      <div class=\"loading\">\n        <img src=\"https://jjbcdn.zaoshu.so/teaclub/chicken-serching.gif\">\n        茶友会正在查询优惠券..\n      </div>\n      <div id=\"No-Result\" style=\"display: none;\">\n        <div class=\"coupon-not-found\">\n          <img src=\"https://jjbcdn.zaoshu.so/teaclub/coupon-not-found.jpg\"/>遗憾，此商品没找到渠道优惠券\n        </div>\n      </div>\n      <div id=\"Coupon-box\" style=\"display: none;\">\n        <dl class=\"prop clear\">\n        <dt class=\"metatit\">优惠券</dt>\n          <dd id=\"Coupon-list\">\n          </dd>\n        </dl>\n      </div>\n      <div id=\"PDD-box\" style=\"display: none;\">\n        <dl class=\"prop clear\">\n        <dt class=\"metatit\">拼多多同款</dt>\n          <dd id=\"PDD-goods\">\n          </dd>\n        </dl>\n      </div>\n      <div id=\"specialEvent-box\" style=\"display: none;\">\n        <dl class=\"prop clear\">\n        <dt class=\"metatit\">活动推荐</dt>\n          <dd id=\"specialEvent-container\">\n          </dd>\n        </dl>\n      </div>\n      <div class=\"information-from\">🍵茶友会提供</div>\n    </div>\n  `);\n\n  if (document.getElementById(\"J_isku\")) {\n    document.getElementsByClassName(\"tb-wrap\")[0].insertBefore(newDiv, document.getElementById(\"J_SepLine\"));\n  } else {\n    document.getElementsByClassName(\"tb-wrap\")[0].insertBefore(newDiv, document.getElementsByClassName(\"tm-ser\")[0]);\n  }\n}\n\nfunction addCouponElement(coupon) {\n  let displayCouponName = coupon.name\n  const couponNameParsingResults = /满([0-9]*)(.[0-9]{2})?元减([0-9]*)(.[0-9]{2})?元/.exec(coupon.name)\n  if (couponNameParsingResults && couponNameParsingResults[2] == \"00\") {\n    displayCouponName = `满${couponNameParsingResults[1]}元减${couponNameParsingResults[3]}元`\n  }\n  const couponQrcode = new QRCode({\n    width: 70,\n    height: 70,\n    background: \"#de1d3d\",\n    content: coupon.shortUrl\n  }).svg();\n  var newDiv = createElementFromHTML(`\n    <a class=\"teaclub-coupon\" href=\"${coupon.url}\" target=\"_blank\">\n      <div class=\"coupon-bonus-item\">\n        <div class=\"coupon-item-left\">\n          <div class=\"qrcode\">${couponQrcode}</div>\n          <p class=\"coupon-item-rmb\">\n            <span class=\"rmb\">${displayCouponName}</span>\n          </p>\n          <p class=\"coupon-item-surplus\">剩余：${coupon.remainCount}</p>\n        </div>\n        <div class=\"coupon-item-right\">\n          <p>有效期</p>\n          <p>${coupon.startTime}</p>\n          <p>- ${coupon.endTime}</p>\n        </div>\n      </div>\n    </a>\n  `);\n  var currentDiv = document.getElementById(\"Coupon-list\");\n  currentDiv.appendChild(newDiv);\n}\n\nfunction buildGoodsBatch(goodsBatch) {\n  injectScriptCode(\n    `\n    var slideIndex = 1;\n\n    // Next/previous controls\n    function plusSlides(n) {\n      showSlides(slideIndex += n);\n    }\n\n    // Thumbnail image controls\n    function currentSlide(n) {\n      showSlides(slideIndex = n);\n    }\n\n    function showSlides(n) {\n      var i;\n      var slides = document.getElementsByClassName(\"teaClubSlide\");\n      var dots = document.getElementsByClassName(\"dot\");\n      if (n > slides.length) {slideIndex = 1}\n      if (n < 1) {slideIndex = slides.length}\n      for (i = 0; i < slides.length; i++) {\n          slides[i].style.display = \"none\";\n      }\n      for (i = 0; i < dots.length; i++) {\n          dots[i].className = dots[i].className.replace(\" active\", \"\");\n      }\n      if (slides[slideIndex-1]) {\n        slides[slideIndex-1].style.display = \"block\";\n        dots[slideIndex-1].className += \" active\";\n      }\n    }\n  `, 'body')\n\n  const goodsBatchDom = goodsBatch.map((goods, index) => {\n    return `<div class=\"teaClubSlide fade\">\n        <div class=\"number-text\">${index} / ${goodsBatch.length}</div>\n        <div class=\"goodCard-list\">\n        ${\n      goods.map((good) => {\n        return buildGoodCard(good)\n      }).join('')\n      }\n        </div>\n      </div>`\n  }).join('')\n  const batchDots = goodsBatch.map((goods, index) => {\n    return `<span class=\"dot\" onclick=\"currentSlide(${index + 1})\"></span>`\n  }).join('')\n\n  let goodsElement = ''\n  if (goodsBatch.length > 1) {\n    goodsElement = createElementFromHTML(`<div id=\"teaclub-slides\">\n      <div class=\"slideshow-container\">\n        ${goodsBatchDom}\n        <a class=\"prev\" onclick=\"plusSlides(-1)\">&#10094;</a>\n        <a class=\"next\" onclick=\"plusSlides(1)\">&#10095;</a>\n      </div>\n      <br>\n      <div style=\"text-align:center\">\n        ${batchDots}\n      </div>\n    </div>`)\n  } else {\n    goodsElement = createElementFromHTML(goodsBatchDom)\n  }\n\n  var currentDiv = document.getElementById(\"PDD-goods\");\n  currentDiv.appendChild(goodsElement);\n}\n\nfunction buildGoodCard(good) {\n  const goodQrcode = new QRCode({\n    width: 100,\n    height: 100,\n    content: good.url\n  }).svg();\n  return `<div>\n    <a class=\"PDD-card\" href=\"${good.url}\" target=\"_blank\">\n    <div class=\"PDD-cardContainer\">\n      <div class=\"PDD-qrcode\">${goodQrcode}</div>\n      <div class=\"PDD-imageContainer PDD-imageContainer--square\">\n        <img class=\"PDD-image\" src=\"${good.thumbnail}\" alt=\"\"/>\n      </div>\n      <div class=\"PDD-info\">\n        <div class=\"PDD-title\">\n          <div class=\"PDD-titleText\">${good.name}</div>\n          <div class=\"PDD-tagList PDD-tagList--title\">\n            <div class=\"PDD-tag PDD-tag--source PDD-tag--jingdong PDD-tag--plain\">销量：${good.sales}</div>\n          </div>\n        </div>\n        <div class=\"PDD-tool\">\n          <div class=\"PDD-toolLeft\">\n            <div class=\"PDD-price\">￥${good.price}</div>\n          </div>\n          <div class=\"PDD-button PDD-button--plain PDD-button--orange\">\n            去购买\n            <svg\n              class=\"Zi Zi--ArrowRight\" fill=\"currentColor\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\">\n              <path\n                d=\"M9.218 16.78a.737.737 0 0 0 1.052 0l4.512-4.249a.758.758 0 0 0 0-1.063L10.27 7.22a.737.737 0 0 0-1.052 0 .759.759 0 0 0-.001 1.063L13 12l-3.782 3.716a.758.758 0 0 0 0 1.063z\"\n                fill-rule=\"evenodd\"></path>\n            </svg>\n          </div>\n        </div>\n      </div>\n    </div>\n  </a>\n  </div>`\n}\n\n\nasync function findCoupon(disable_find_coupon) {\n  if (disable_find_coupon) return\n  addDiscountElement()\n  const urlParams = new URLSearchParams(window.location.search);\n  const sku = urlParams.get('id') || urlParams.get('skuId')\n  const title = document.title.split('-')[0]\n  const merchant = window.location.host.indexOf('item.taobao.com') > -1 ? 'taobao' : 'tmall'\n  chrome.runtime.sendMessage({\n    action: \"queryCoupon\",\n    params: {\n      merchant,\n      sku,\n      title\n    }\n  })\n}\n\nfunction markCheckinStatus(task, data, cb) {\n  chrome.runtime.sendMessage({\n    action: \"markCheckinStatus\",\n    taskId: task.id,\n    status: \"signed\",\n    ...data\n  }, function (response) {\n    console.log('markCheckinStatus response', response)\n    if (cb && response) { cb() }\n  });\n}\n\n\n// *********\n// 签到任务\n// *********\n\n// 飞猪里程\nfunction markFliggyCheckin(task, orderId) {\n  const signRes = document.getElementsByClassName(\"tlc-title\")[0] && document.getElementsByClassName(\"tlc-title\")[0].innerText\n  const value = (document.getElementsByClassName(\"tlc-title\")[0] && document.getElementsByClassName(\"tlc-title\")[0].getElementsByTagName(\"span\")[0]) ? document.getElementsByClassName(\"tlc-title\")[0].getElementsByTagName(\"span\")[0].innerText : null\n\n  console.log('markFliggyCheckin', task, orderId, signRes, value)\n  if (signRes && (signRes.indexOf(\"获得\") > -1)) {\n    return markCheckinStatus(task, {\n      value: value + '里程',\n      orderId: orderId\n    }, () => {\n      chrome.runtime.sendMessage({\n        action: \"checkin_notice\",\n        value: value,\n        reward: 'mileage',\n        title: orderId ? \"茶友会自动为您签订单奖励里程\" : \"茶友会自动为您签到领里程\",\n        content: \"恭喜您获得了\" + value + '个里程奖励'\n      }, function (response) {\n        console.log(\"Response: \", response);\n      })\n    })\n  } else if (signRes && (signRes.indexOf(\"今日已领\") > -1)) {\n    markCheckinStatus(task, {\n      value\n    })\n  } else if (signRes && (signRes.indexOf(\"已领取过\") > -1) && orderId) {\n    markCheckinStatus(task, {\n      value,\n      orderId: orderId\n    })\n  } else if (signRes && (signRes.indexOf(\"本月您已领满\") > -1)) {\n    markCheckinStatus(task, {\n      value,\n      month: new Date().getMonth(),\n    })\n  }\n}\n\nfunction fliggyCheckin(setting) {\n  if (setting != 'never') {\n    weui.toast('茶友会运行中', 1000);\n    chrome.runtime.sendMessage({\n      action: \"updateRunStatus\",\n      taskId: 2\n    })\n    let signInButton = document.getElementsByClassName(\"J_mySignInBtn\")[0]\n\n    if (signInButton && signInButton.innerText == \"已签到\") {\n      markCheckinStatus({\n        key: 'fliggy-mytrip',\n        id: 2\n      })\n    } else if (signInButton && signInButton.innerText && signInButton.innerText.indexOf(\"签到\") > -1) {\n      simulateClick(signInButton)\n      // 监控结果\n      observeDOM(document.body, function () {\n        markFliggyCheckin({\n          key: 'fliggy-mytrip',\n          id: 2\n        })\n      })\n    }\n  }\n}\n\nfunction fliggyCheckin2(setting) {\n  if (setting != 'never') {\n    weui.toast('茶友会运行中', 1000);\n    chrome.runtime.sendMessage({\n      action: \"updateRunStatus\",\n      taskId: 3\n    })\n    let signInButton = document.getElementsByClassName(\"J_makesurebuttontvipBtn\")[0]\n    if (signInButton && signInButton.innerText && signInButton.innerText == \"确    认\") {\n      simulateClick(signInButton)\n      // 监控结果\n      observeDOM(document.body, function () {\n        markFliggyCheckin({\n          key: 'fliggy-tvip',\n          id: 3\n        })\n      })\n    } else {\n      if (signInButton && signInButton.innerText == \"已签到\") {\n        markCheckinStatus({\n          key: 'fliggy-tvip',\n          id: 3\n        })\n      }\n    }\n  }\n}\n\nfunction fliggyCheckin3(setting) {\n  if (setting != 'never') {\n    weui.toast('茶友会运行中', 1000);\n    chrome.runtime.sendMessage({\n      action: \"updateRunStatus\",\n      taskId: 4\n    })\n    let signInButton = null\n    let signInReward = null\n    let spanElements = document.getElementsByTagName(\"span\")\n    Array.prototype.slice.call(spanElements).forEach(function (element) {\n      if (element.innerText && /^签到\\+[0-9]+里程/.test(element.innerText)) {\n        signInButton = element\n      }\n      if (element.innerText && /^明日\\+[0-9]+里程/.test(element.innerText)) {\n        signInReward = element\n      }\n    });\n    console.log('signInButton', signInButton)\n    if (signInButton) {\n      setTimeout(() => {\n        simulateClick(signInButton, true)\n      }, 500);\n      // 监控结果\n      observeDOM(document.body, function () {\n        markFliggyCheckin({\n          key: 'rx-member',\n          id: 4\n        })\n      })\n    } else {\n      if (signInReward && signInReward.innerText) {\n        markCheckinStatus({\n          key: 'rx-member',\n          id: 4\n        })\n      }\n    }\n  }\n}\n\nfunction fliggyCheckin6(setting) {\n  if (setting != 'never') {\n    weui.toast('茶友会运行中', 1000);\n    chrome.runtime.sendMessage({\n      action: \"updateRunStatus\",\n      taskId: 6\n    })\n    const urlParams = new URLSearchParams(window.location.search);\n    let orderId = urlParams.get('orderId')\n    let signInButton = document.getElementsByClassName(\"J_makesurebuttontvip\")[0]\n    if (signInButton && signInButton.innerText && signInButton.innerText == \"确    认\") {\n      simulateClick(signInButton)\n      // 监控结果\n      observeDOM(document.body, function () {\n        markFliggyCheckin({\n          key: 'order-fliggy',\n          id: 6\n        }, orderId)\n      })\n    }\n  }\n}\n\nfunction fliggyCheckin7(setting) {\n  if (setting != 'never') {\n    weui.toast('茶友会运行中', 1000);\n    chrome.runtime.sendMessage({\n      action: \"updateRunStatus\",\n      taskId: 7\n    })\n    let signInButton = document.getElementsByClassName(\"check-btn\")[0]\n    if (signInButton && signInButton.innerText && signInButton.innerText == \"立即签到\") {\n      simulateClick(signInButton)\n      // 监控结果\n      observeDOM(document.body, function () {\n        markFliggyCheckin({\n          key: 'welfare-center',\n          id: 7\n        })\n      })\n    } else {\n      if (signInButton && signInButton.innerText == \"上飞猪App领更多\") {\n        markCheckinStatus({\n          key: 'welfare-center',\n          id: 7\n        })\n      }\n    }\n  }\n}\n\nfunction accountAlive(type, message) {\n  chrome.runtime.sendMessage({\n    action: \"saveLoginState\",\n    state: \"alive\",\n    message: message,\n    type: type\n  }, function (response) {\n    console.log(\"accountAlive \", type, message, response);\n  });\n}\n\nif (document.getElementById(\"login-info\")) {\n  observeDOM(document.getElementById(\"login-info\"), function () {\n    if (document.getElementsByClassName(\"j_Username\")[0] && document.getElementsByClassName(\"j_Username\")[0].innerText) {\n      accountAlive('pc', 'PC网页检测到用户名')\n    }\n  });\n}\n\n// 主任务\nfunction CheckDom() {\n  if (window.location.host.indexOf(\"m.taobao.com\") > -1 && window.location.host.indexOf(\"item.taobao.com\") < 0) {\n    if (window.location.host != \"market.m.taobao.com\") {\n      injectScript(chrome.extension.getURL('/static/touch-emulator.js'), 'body');\n      injectScriptCode(`\n        setTimeout(function () {\n          TouchEmulator();\n        }, 200)\n      `, 'body')\n    }\n  }\n  // 判断登录状态\n  setTimeout(() => {\n    checkLoginState()\n  }, 1000)\n\n  setTimeout(() => {\n    if (window.location.host == 'login.taobao.com') {\n      chrome.runtime.sendMessage({\n        action: \"saveLoginState\",\n        state: \"failed\",\n        message: \"PC网页需要登录\",\n        type: \"pc\"\n      }, function (response) {\n        console.log(\"Response: \", response);\n      });\n    }\n    if (window.location.host == 'login.m.taobao.com') {\n      chrome.runtime.sendMessage({\n        action: \"saveLoginState\",\n        state: \"failed\",\n        message: \"移动网页需要登录\",\n        type: \"m\"\n      }, function (response) {\n        console.log(\"Response: \", response);\n      });\n    }\n  }, 8000);\n\n  // 订单\n  if (document.title == \"已买到的宝贝\" && window.location.host == 'buyertrade.taobao.com') {\n    let orderElements = document.getElementsByClassName(\"bought-wrapper-mod__head-info-cell___29cDO\")\n    let time = 0\n    // 只处理最近五个订单\n    if (orderElements && orderElements.length > 5) {\n      orderElements = Array.prototype.slice.call(orderElements).slice(0, 5);\n    }\n    if (orderElements) {\n      accountAlive('pc', 'PC网页检测订单')\n    }\n    Array.prototype.slice.call(orderElements).forEach(function (orderElement) {\n      if (orderElement.lastElementChild && orderElement.lastElementChild.lastElementChild) {\n        let orderId = orderElement.lastElementChild.lastElementChild.innerText\n        if (orderId) {\n          setTimeout(function () {\n            chrome.runtime.sendMessage({\n              action: \"getOrderFliggy\",\n              orderId: orderId\n            }, function (response) {\n              console.log(\"Response: \", response);\n            });\n          }, time)\n          time += 15000;\n        }\n      }\n    });\n  }\n\n  // 商品页\n  if (window.location.host.indexOf('item.taobao.com') > -1 || window.location.host.indexOf('detail.tmall.com') > -1) {\n    setTimeout(() => {\n      getSetting('disable_find_coupon', (setting) => {\n        findCoupon(setting)\n      })\n    }, 50);\n  }\n  // 飞猪签到\n  if (document.title == \"我的旅行\" && window.location.host == 'www.fliggy.com') {\n    setTimeout(() => {\n      if (document.getElementsByClassName(\"J_mySignInBtn\")[0]) {\n        getSetting('task-2_frequency', fliggyCheckin)\n      } else if (document.getElementsByClassName(\"J_makesurebuttontvipBtn\")[0]) {\n        getSetting('task-3_frequency', fliggyCheckin2)\n      } else if (document.getElementsByClassName(\"J_makesurebuttontvip\")[0]) {\n        getSetting('task-6_frequency', fliggyCheckin6)\n      }\n    }, 3000);\n  };\n  if (document.title == \"会员中心\" && window.location.host == 'h5.m.taobao.com') {\n    getSetting('task-4_frequency', fliggyCheckin3)\n  }\n  if (document.title == \"里程福利中心\" && window.location.host == 'h5.m.taobao.com') {\n    getSetting('task-7_frequency', fliggyCheckin7)\n  }\n}\n\n\n// 检查登录状态\nfunction checkLoginState() {\n  // PC 是否登录\n  if (document.getElementById(\"mtb-nickname\") && document.getElementById(\"mtb-nickname\").value || document.getElementsByClassName(\"J_MemberNick\")[0]) {\n    accountAlive('pc', 'PC网页检测到用户名')\n  }\n  if (document.getElementById(\"J_SiteNavLogin\")) {\n    if (document.getElementById(\"J_SiteNavLogin\").querySelector(\".site-nav-login-info-nick\") && document.getElementById(\"J_SiteNavLogin\").querySelector(\".site-nav-login-info-nick\").text) {\n      accountAlive('pc', 'PC网页检测到用户头像')\n    }\n  }\n  // M 是否登录\n  if (document.getElementsByClassName(\"tb-toolbar-container\")[0] || window.location.href == \"https://h5.m.taobao.com/mlapp/mytaobao.html\") {\n    accountAlive('m', '移动端打开我的淘宝')\n  }\n  if (window.location.href == \"https://main.m.taobao.com/mytaobao/index.html\") {\n    if (document.getElementsByClassName(\".main-layout\")[0].querySelector(\".tpl-wrapper\")) {\n      accountAlive('m', '移动端打开我的淘宝')\n    }\n  }\n}\n\n\n$(document).ready(function () {\n  console.log('茶友会注入页面成功');\n  checkLoginState()\n  if (!pageTaskRunning) {\n    setTimeout(function () {\n      console.log('茶友会开始执行任务');\n      CheckDom()\n    }, 2500)\n  }\n});\n\n\nfunction dealWithSearchRes(content) {\n  if (content.coupon) {\n    setTimeout(() => {\n      document.getElementById(\"Coupon-box\").style.display = 'block';\n      addCouponElement(content.coupon)\n      document.getElementById(\"teaclub\").getElementsByClassName(\"loading\")[0].style.display = 'none';\n    }, 500);\n  } else {\n    document.getElementById(\"Coupon-box\").style.display = 'none';\n  }\n  if (content.specialEvent && content.specialEvent.html) {\n    document.getElementById(\"specialEvent-box\").style.display = 'block';\n    const specialEventElement = createElementFromHTML(content.specialEvent.html)\n\n    const containerDiv = document.getElementById(\"specialEvent-container\");\n    containerDiv.appendChild(specialEventElement);\n  }\n  if (content.similarGoods && content.similarGoods.length > 0) {\n    setTimeout(() => {\n      document.getElementById(\"teaclub\").getElementsByClassName(\"loading\")[0].style.display = 'none';\n      document.getElementById(\"PDD-box\").style.display = 'block';\n      buildGoodsBatch(content.similarGoods.chunk(3))\n    }, 500);\n    setTimeout(() => {\n      injectScriptCode(`\n        showSlides(1);\n      `, 'body')\n    }, 520);\n  } else {\n    document.getElementById(\"PDD-box\").style.display = 'none';\n  }\n  if (!content.coupon && (!content.similarGoods || content.similarGoods.length < 1)) {\n    setTimeout(() => {\n      document.getElementById(\"teaclub\").getElementsByClassName(\"loading\")[0].style.display = 'none';\n      document.getElementById(\"No-Result\").style.display = 'block';\n    }, 1500);\n  }\n}\n\n// 应用消息\nchrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {\n  console.log('onMessage', message)\n  switch (message.type) {\n    case 'couponInfo':\n      dealWithSearchRes(message.content)\n      break;\n    default:\n      break;\n  }\n})\n\n\n// 消息\nvar passiveSupported = false;\ntry {\n  var options = Object.defineProperty({}, \"passive\", {\n    get: function () {\n      passiveSupported = true;\n    }\n  });\n\n  window.addEventListener(\"test\", null, options);\n} catch (err) { }\n\nwindow.addEventListener(\"message\", function (event) {\n  if (event.data && event.data.action == 'productPrice') {\n    findOrderBySkuAndApply(event.data, event.data.setting)\n  }\n},\n  passiveSupported ? { passive: true } : false\n);\n\n\nvar nodeList = document.querySelectorAll('script');\nfor (var i = 0; i < nodeList.length; ++i) {\n  var node = nodeList[i];\n  node.src = node.src.replace(\"http://\", \"https://\")\n}"
  },
  {
    "path": "src/popup.js",
    "content": "import * as _ from \"lodash\"\n$ = window.$ = window.jQuery = require('jquery')\nimport 'weui'\nimport weui from 'weui.js'\nimport Vue from 'vue'\nimport microtip from 'microtip/microtip.css'\n\nimport '../static/style/popup.css'\n\n$.each(['show', 'hide'], function (i, ev) {\n  var el = $.fn[ev];\n  $.fn[ev] = function () {\n    this.trigger(ev);\n    return el.apply(this, arguments);\n  };\n});\n\nimport App from './components/app.vue';\nimport VueLazyload from 'vue-lazyload'\n\nVue.use(VueLazyload)\nnew Vue({\n  el: '#app',\n  render: h => h(App)\n})\n\n// 消息已读\nfunction readMessage() {\n  chrome.runtime.sendMessage({\n    text: \"clearUnread\"\n  }, function (response) {\n    console.log(\"Response: \", response);\n  });\n}\n\n\n$( document ).ready(function() {\n  // 标记已读\n  readMessage()\n\n  // 查询最新版本\n  $.getJSON(`https://teaclub.zaoshu.so/updates?buildid=${process.env.BUILDID}&browser=${process.env.BROWSER}&app=teaclub`, function (lastVersion) {\n    if (!lastVersion) return localStorage.removeItem('newVersion')\n    let skipBuildId = localStorage.getItem('skipBuildId')\n    let localBuildId = skipBuildId || process.env.BUILDID\n    // 如果有新版\n    if (localBuildId < lastVersion.buildId) {\n      localStorage.setItem('newVersion', lastVersion.versionCode)\n      // 如果新版是主要版本，而且当前版本需要被提示\n      if (lastVersion.major && localBuildId < lastVersion.noticeBuildId) {\n        let noticeDialog = weui.dialog({\n          title: `${lastVersion.title} <span class=\"dismiss\">&times;</span>` || '有版本更新',\n          content: `${lastVersion.changelog}\n            <div class=\"changelog\">\n              <span class=\"time\">${lastVersion.time}</span>` +\n             (lastVersion.blogUrl ? `<a class=\"blog\" href=\"${lastVersion.blogUrl}\" target=\"_blank\">了解更多</a>` : '') +\n            `</div>`,\n          className: 'update',\n          buttons: [{\n            label: '不再提醒',\n            type: 'default',\n            onClick: function () {\n              localStorage.setItem('skipBuildId', lastVersion.buildId)\n            }\n          }, {\n            label: '下载更新',\n            type: 'primary',\n            onClick: function () {\n              chrome.tabs.create({\n                url: lastVersion.downloadUrl || `https://teaclub.zaoshu.so/updates/latest?browser=${process.env.BROWSER}&app=teaclub`\n              })\n            }\n          }]\n        });\n        $(\".update .dismiss\").on(\"click\", function () {\n          noticeDialog.hide()\n        })\n      }\n    } else {\n      localStorage.removeItem('newVersion')\n    }\n  });\n\n\n  $('.settings .weui-navbar__item').on('click', function () {\n    $(this).addClass('weui-bar__item_on').siblings('.weui-bar__item_on').removeClass('weui-bar__item_on');\n    var type = $(this).data('type')\n    $('.settings_box').hide()\n    $('.settings_box.' + type).show()\n  });\n\n  $(document).on(\"click\", \".openMobilePage\", function () {\n    chrome.runtime.sendMessage({\n      action: \"openUrlAsMobile\",\n      url: $(this).data('url')\n    }, function (response) {\n      console.log(\"Response: \", response);\n    });\n  })\n\n  $(\".weui-dialog__ft a\").on(\"click\", function () {\n    $(\"#dialogs\").hide()\n    $(\"#listenAudio\").hide()\n    $(\"#changeLogs\").hide()\n  })\n\n  $(\"#dialogs .js-close\").on(\"click\", function () {\n    $(\"#dialogs\").hide()\n  })\n})\n\n// 防止缩放\nchrome.tabs.getZoomSettings(function (zoomSettings) {\n  if (zoomSettings.defaultZoomFactor > 1 && zoomSettings.scope == 'per-origin' && zoomSettings.mode == 'automatic') {\n    let zoomPercent = (100 / (zoomSettings.defaultZoomFactor * 100)) * 100;\n    document.body.style.zoom = zoomPercent + '%'\n  }\n})\n"
  },
  {
    "path": "src/start.js",
    "content": "import 'weui'\nimport '../static/style/start.css'\n"
  },
  {
    "path": "src/tasks.js",
    "content": "import {DateTime} from 'luxon'\nimport { getLoginState } from './account'\nimport { getSetting, readableTime } from './utils'\n\nconst frequencyOptionText = {\n  '2h': \"每2小时\",\n  '5h': \"每5小时\",\n  'daily': \"每天\",\n  'never': \"从不\"\n}\nconst mapFrequency = {\n  '2h': 2 * 60,\n  '5h': 5 * 60,\n  'daily': 24 * 60,\n  'never': 99999\n}\n\nconst tasks = [\n  {\n    id: '6',\n    src: {\n      pc: 'https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm',\n    },\n    title: '订单里程',\n    description: \"每个淘宝订单可以领取3个飞猪里程（每月可领5次）\",\n    mode: 'iframe',\n    key: \"order-fliggy\",\n    type: ['pc'],\n    checkin: true,\n    frequencyOption: ['daily', 'never'],\n    frequency: 'daily',\n    rateLimit:{\n      daily: 5,\n      hour: 2\n    }\n  },\n  {\n    id: '2',\n    src: {\n      pc: 'https://www.fliggy.com/mytrip/',\n    },\n    baseUrl: \"https://www.fliggy.com/mytrip/\",\n    title: '飞猪里程1',\n    description: \"每日签到领取飞猪里程\",\n    mode: 'iframe',\n    key: \"fliggy-mytrip\",\n    type: ['pc'],\n    checkin: true,\n    frequencyOption: ['daily', 'never'],\n    frequency: 'daily',\n    rateLimit:{\n      daily: 5,\n      hour: 2\n    }\n  },\n  {\n    id: '3',\n    src: {\n      pc: 'https://www.fliggy.com/mytrip/?tvm=tvip',\n    },\n    title: '飞猪里程2',\n    description: \"每日签到领取飞猪里程\",\n    mode: 'iframe',\n    key: \"fliggy-tvip\",\n    type: ['pc'],\n    checkin: true,\n    frequencyOption: ['daily', 'never'],\n    frequency: 'daily',\n    rateLimit:{\n      daily: 5,\n      hour: 2\n    }\n  },\n  {\n    id: '4',\n    src: {\n      m: 'https://h5.m.taobao.com/trip/rx-member/index/index.html?_projVer=0.1.25',\n    },\n    title: '飞猪里程3',\n    description: \"飞猪移动页每日签到里程\",\n    mode: 'iframe',\n    key: \"rx-member\",\n    type: ['m'],\n    checkin: true,\n    frequencyOption: ['daily', 'never'],\n    frequency: 'daily',\n    rateLimit:{\n      daily: 5,\n      hour: 2\n    }\n  },\n  {\n    id: '7',\n    src: {\n      m: 'https://h5.m.taobao.com/trip/welfare-center/mileage/index.html',\n    },\n    title: '飞猪里程5',\n    description: \"飞猪签到领里程\",\n    mode: 'iframe',\n    key: \"welfare-center\",\n    type: ['m'],\n    checkin: true,\n    frequencyOption: ['daily', 'never'],\n    frequency: 'daily',\n    rateLimit:{\n      daily: 5,\n      hour: 2\n    }\n  }\n]\n\n// 根据登录状态选择任务模式\nlet findTaskPlatform = function (task) {\n  let loginState = getLoginState()\n  let platform = null\n  if (loginState.class == 'alive') {\n    platform = task.type[0];\n  }\n  return platform\n}\n\nlet getTask = function (taskId, currentPlatform) {\n  let taskParameters = getSetting('teaclub:task-parameters', [])\n  let parameters = (Array.isArray(taskParameters) && taskParameters.length > 0) ? taskParameters.find(t => t.id == taskId.toString()) : {}\n  let task = Object.assign({}, tasks.find(t => t.id == taskId.toString()), parameters)\n  let taskStatus = {}\n  let year = new Date().getFullYear()\n  let today = DateTime.local().toFormat(\"o\")\n  let hour = new Date().getHours()\n  taskStatus.usage = {\n    hour: getSetting(`temporary:usage-${taskId}_${year}d:${today}:h:${hour}`, 0),\n    daily: getSetting(`temporary:usage-${taskId}_${year}d:${today}`, 0)\n  }\n  taskStatus.platform = findTaskPlatform(task);\n  taskStatus.frequency = getSetting(`task-${taskId}_frequency`, task.frequency)\n  taskStatus.last_run_at = localStorage.getItem(`task-${task.id}_lasttime`) ? parseInt(localStorage.getItem(`task-${task.id}_lasttime`)) : null\n  taskStatus.last_run_description = taskStatus.last_run_at ? \"上次运行： \" + readableTime(DateTime.fromMillis(Number(taskStatus.last_run_at))) : \"从未执行\";\n  // 如果是签到任务，则读取签到状态\n  if (task.checkin) {\n    let checkinRecord = getSetting(`checkin_${task.key}`, null)\n    if (checkinRecord && checkinRecord.date == DateTime.local().toFormat(\"o\")) {\n      taskStatus.checked = true\n      taskStatus.checkin_description = \"完成于：\" + readableTime(DateTime.fromISO(checkinRecord.time)) + (checkinRecord.value ? \"，领到：\" + checkinRecord.value : \"\");\n    }\n  }\n  // 订单里程任务每月5次\n  if (task.id == \"6\") {\n    let year = new Date().getFullYear()\n    let month = new Date().getMonth()\n    let monthStatus = localStorage.getItem(`order-fliggy-${year}-${month}`)\n    if (monthStatus && monthStatus == 'Y') {\n      taskStatus.checked = true\n      taskStatus.checkin_description = \"本月已领取五次\"\n    }\n  }\n  // 如果限定平台\n  if (currentPlatform) {\n    if (task.type && task.type.indexOf(currentPlatform) < 0) {\n      taskStatus.unavailable = true\n    }\n  }\n  // 选择运行平台\n  if (!task.url) {\n    taskStatus.url = taskStatus.platform ? task.src[taskStatus.platform] : task.src[task.type[0]];\n  }\n  // 如果任务无可运行平台\n  if (!taskStatus.platform) {\n    taskStatus.suspended = true;\n    taskStatus.platform = task.type[0];\n  }\n  // 如果超出限制\n  if (taskStatus.usage.daily >= task.rateLimit.daily || taskStatus.usage.hour >= task.rateLimit.hour) {\n    taskStatus.pause = true;\n  }\n  return Object.assign(task, taskStatus)\n}\n\nlet getTasks = function (currentPlatform) {\n  let taskList = tasks.map((task) => {\n    return getTask(task.id, currentPlatform)\n  })\n  return taskList.filter(task => !(task.unavailable || task.deprecated));\n}\n\nexport {\n  frequencyOptionText,\n  mapFrequency,\n  tasks,\n  getTask,\n  getTasks,\n  findTaskPlatform\n};\n"
  },
  {
    "path": "src/utils.js",
    "content": "import { DateTime } from 'luxon'\n\nexport const rand = function (n) {\n  return (Math.floor(Math.random() * n + 1));\n}\nexport const price = function (price) {\n  return Number(Number(price).toFixed(2))\n}\nexport const getSetting = function (settingKey, defaultValue) {\n  let setting = localStorage.getItem(settingKey)\n  if (setting) {\n    try {\n      setting = JSON.parse(setting)\n    } catch (error) { }\n  }\n  return setting ? setting : defaultValue\n}\nexport const saveSetting = function (settingKey, value) {\n  return localStorage.setItem(settingKey, JSON.stringify(value))\n}\nexport const readableTime = function (dateTime) {\n  if (DateTime.local().hasSame(dateTime, 'day')) {\n    return '今天 ' + dateTime.setLocale('zh-cn').toLocaleString(DateTime.TIME_SIMPLE)\n  }\n  if (DateTime.local().hasSame(dateTime.plus({ days: 1 }), 'day')) {\n    return '昨天 ' + dateTime.setLocale('zh-cn').toLocaleString(DateTime.TIME_SIMPLE)\n  }\n  return dateTime.setLocale('zh-cn').toFormat('f')\n}\nexport const versionCompare = function (v1, v2, options) {\n  var lexicographical = options && options.lexicographical,\n    zeroExtend = options && options.zeroExtend,\n    v1parts = v1.split('.'),\n    v2parts = v2.split('.');\n  function isValidPart(x) {\n    return (lexicographical ? /^\\d+[A-Za-z]*$/ : /^\\d+$/).test(x);\n  }\n  if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {\n    return NaN;\n  }\n  if (zeroExtend) {\n    while (v1parts.length < v2parts.length) v1parts.push(\"0\");\n    while (v2parts.length < v1parts.length) v2parts.push(\"0\");\n  }\n  if (!lexicographical) {\n    v1parts = v1parts.map(Number);\n    v2parts = v2parts.map(Number);\n  }\n  for (var i = 0; i < v1parts.length; ++i) {\n    if (v2parts.length == i) {\n      return 1;\n    }\n    if (v1parts[i] == v2parts[i]) {\n      continue;\n    }\n    else if (v1parts[i] > v2parts[i]) {\n      return 1;\n    }\n    else {\n      return -1;\n    }\n  }\n  if (v1parts.length != v2parts.length) {\n    return -1;\n  }\n  return 0;\n}"
  },
  {
    "path": "src/variables.js",
    "content": "\nmodule.exports = {\n  stateText: {\n    \"failed\": \"失败\",\n    \"alive\": \"有效\",\n    \"unknown\": \"未知\"\n  },\n  recommendServices: [\n    {\n      link: \"https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=8c3eff7793dd70781315d9b5c9727c39&from=console\",\n      title: \"腾讯云新客礼包\",\n      description: \"新客户无门槛领取2775元代金券\",\n      class: \"el-tag el-tag--success\"\n    },\n    {\n      link: \"https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=sqj7d3bm\",\n      title: \"阿里云优惠券\",\n      description: \"领取阿里云全品类优惠券\",\n      class: \"el-tag\"\n    },\n  ]\n};"
  },
  {
    "path": "static/style/popup.css",
    "content": "\n@media (prefers-color-scheme:dark) {\n  body:not([data-weui-theme='light']) {\n    background-color: #1a1919;\n    color: #999;\n  }\n\n  body:not([data-weui-theme='light']) .messages, body:not([data-weui-theme='light']) .discounts {\n    background-color: transparent;\n  }\n\n  body:not([data-weui-theme='light']) .weui-navbar__item.weui-bar__item_on.zaoshu-tab {\n    color: #b9b9b9;\n    background: #661c1a91;\n  }\n}\n\n@media (prefers-color-scheme:light) {\n  body:not([data-weui-theme='dark']) {\n    background-color: #fff;\n    color: #333;\n    transition: background-color 0.3s ease;\n  }\n}\n\nselect {\n  text-indent: 0.01px;\n  text-overflow: '';\n  -moz-appearance: none;\n}\n\na {\n  color: #0b85c3;\n}\n\na:hover {\n  color: #006da5;\n}\n\nhtml, body {\n  min-height: 580px;\n  min-width: 780px;\n  overflow-x: hidden;\n  overflow-y: hidden;\n}\n\n.popup{\n  height: 600px;\n  width: 800px;\n  overflow: hidden;\n}\n\n\n.weui-cell_select {\n  height: 34px;\n  font-size: 15px;\n  padding: 2px 15px;\n}\n\n.frequency_settings .weui-cell__bd i.show {\n  font-size: 20px;\n  margin-left: -5px;\n  display: inline-block !important;\n  height: 20px;\n  margin-top: -2px;\n}\n\n.page__hd {\n  padding: 10px;\n}\n\n.weui-dialog {\n  max-width: 440px;\n}\n\n.page__desc {\n  padding: 5px 10px;\n}\n\n.settings {\n  width: 45%;\n  background: #cccccc1f;\n  border-right: 1px solid var(--weui-FG-3);\n}\n\n.settings .weui-cell_switch {\n  height: 32px;\n  font-size: 15px;\n  padding-top: 4px;\n  padding-bottom: 4px;\n}\n\n.contents {\n  width: 55%;\n  overflow: hidden;\n  height: 600px;\n}\n\n.weui-navbar__item.weui-bar__item_on.zaoshu-tab {\n  color: #921714;\n}\n\n.weui-navbar__item.weui-bar__item_on{\n  color: var(--weui-BG-4);\n  font-weight: bold;\n}\n\n.contents .weui-tab {\n  height: 550px;\n}\n\n.weui-cell_switch {\n  height: 32px;\n  font-size: 16px;\n}\n\n.contents .weui-tab .weui-badge {\n  margin-left: 5px;\n  margin-top: -3px;\n  background-color: #4CAF50;\n}\n\n.contents .weui-tab .weui-badge.new-discounts {\n  background-color: #b9201d;\n  padding: 0.3em;\n  position: absolute;\n}\n\n.other_actions {\n  padding: 10px;\n  padding-bottom: 0;\n}\n\n.other_actions p {\n  padding: 5px 0;\n}\n\n.no_order, .no_message {\n  background: url(../image/empty.svg) no-repeat center 10px;\n  padding: 5em 0em;\n  text-align: center;\n  margin-top: 10em;\n  opacity: 0.5;\n  padding-top: 7em;\n}\n\n.no_message .tips{\n  font-size: 12px;\n  margin-top: .5em;\n}\n\n.bottom-tips {\n  padding: 5px;\n}\n\n.other_actions h3 {\n  font-size: 16px;\n}\n\n.recommendation {\n  height: 210px;\n  font-size: 12px;\n}\n\n.tips .weui-btn_mini {\n  padding: 0.1em .5em;\n  line-height: 1.4;\n  margin-bottom: -7px;\n  font-size: 12px;\n}\n\n.reward_tips .newyear {\n  color: #f15f5f;\n}\n\n#renderFrame {\n  height: 0px;\n}\n\n.reload-icon {\n  cursor: pointer;\n}\n\n.frequency_settings .weui-cell__bd {\n  line-height: 34px;\n}\n\n.frequency_settings .weui-icon-waiting-circle {\n  font-size: 19px;\n}\n\n.switch-paymethod {\n  cursor: pointer;\n}\n\n#notice {\n  color: #333;\n}\n\n.alipay_action {\n  line-height: 26px;\n  height: 24px;\n  width: 120px;\n  margin: 0 auto;\n}\n\n.alipay_action svg {\n  float: left;\n}\n\n\n.reload-icon {\n  background: url(../image/reload.svg) no-repeat 1px 1px;\n  width: 20px;\n  height: 21px;\n  color: #dcdcdc;\n  display: inline-block;\n  vertical-align: middle;\n  background-size: 17px;\n}\n\n.orders li, .messages li {\n  display: block;\n}\n\n.orders .order_time {\n  position: relative;\n}\n\n.orders .show-order {\n  -webkit-mask: url(../image/show.svg) no-repeat center;\n  mask: url(../image/show.svg) no-repeat center;\n  -webkit-mask-size: 16px;\n  mask-size: 16px;\n}\n\n.orders .show-order, .orders .hide-order {\n  width: 20px;\n  height: 21px;\n  color: #dcdcdc;\n  display: inline-block;\n  vertical-align: middle;\n  background-size: 16px;\n  cursor: pointer;\n  background-color: #ccc;\n  position: absolute;\n  right: 5px;\n  top: 1px;\n}\n\n.orders .hide-order {\n  -webkit-mask: url(../image/hide.svg) no-repeat center;\n  mask: url(../image/hide.svg) no-repeat center;\n  -webkit-mask-size: 16px;\n  mask-size: 16px;\n}\n\n.logo {\n  display: inline-block;\n}\n\n.order_time {\n  margin-top: .77em;\n  margin-bottom: .3em;\n  padding-left: 15px;\n  padding-right: 15px;\n  color: #999;\n  font-size: 12px;\n  padding-top: 5px;\n}\n\n#orders .weui-cell:before, .contents-box.weui-cells:before, .contents-box.weui-cells:after {\n  border-top: none;\n  content: none;\n}\n\n.orders .good_title {\n  height: 55px;\n}\n\n.good_title {\n  font-size: 12px;\n  height: 80px;\n  display: block;\n  clear: both;\n  width: 98%;\n}\n\n.orders .good_title p {\n  margin-left: 65px;\n}\n\n.self-recommendation p.tips {\n  font-size: 12px;\n  text-align: center;\n  padding: 1em;\n  color: #ccc;\n}\n\n.good_title p {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: -webkit-box;\n  max-height: 78px;\n  -webkit-line-clamp: 3;\n  -moz-box-orient: vertical;\n  -webkit-box-orient: vertical;\n  padding-bottom: 5px;\n  line-height: 18px;\n  margin-left: 85px;\n}\n\n.orders .good_title img {\n  width: 55px;\n  height: 55px;\n}\n\n.good_title img {\n  display: inline-block;\n  position: absolute;\n  left: 15px;\n  top: 10px;\n  padding-right: 10px;\n  width: 75px;\n  height: 75px;\n  overflow: hidden;\n}\n\n.good_title .description {\n  font-size: 12px;\n  color: #666;\n  max-height: 35px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: -webkit-box;\n  -webkit-line-clamp: 2;\n  -moz-box-orient: vertical;\n  -webkit-box-orient: vertical;\n}\n\n.good_title span.count {\n  color: #949494;\n}\n\nimg.promotion_title {\n  display: inline-block;\n  padding-right: 10px;\n  width: 55px;\n  height: 55px;\n  overflow: hidden;\n  float: left;\n  position: unset;\n}\n\n.good_title a {\n  font-size: 13px;\n}\n\n.promotion_price {\n  font-size: 14px;\n  color: #1aad19;\n  display: block;\n  font-weight: 500;\n  padding-bottom: 5px;\n}\n\n.changelog .time {\n  font-size: 12px;\n  color: #666;\n}\n\n.changelog .blog {\n  float: right;\n  color: #888;\n  font-size: 12px;\n  text-decoration: none;\n  line-height: 24px;\n}\n\n.go-buy {\n  background: #1aad19;\n  color: #fff;\n  font-size: 14px;\n  padding: .3em .5em;\n  border-radius: 2px;\n}\n\n.orders .dismiss {\n  float: right;\n  padding: 2px;\n  position: absolute;\n  top: -2px;\n  right: 6px;\n  font-size: 14px;\n  cursor: pointer;\n}\n\n.weui-cell.promotion {\n  background: #feedba54;\n}\n\n.buy-btn {\n  padding: 2px 5px;\n  line-height: 1.3;\n  font-size: 12px;\n  margin-top: 4px;\n}\n\na.buy-btn:hover {\n  color: #efefef;\n}\n\n.good {\n  background: #fdfdfd;\n  border-bottom: 1px solid #f3f3f3;\n  border-top: 1px solid #f3f3f3;\n}\n\n.good+.good {\n  border-top: none;\n}\n\n.success_log {\n  font-size: 12px;\n  margin: 10px;\n  color: #690;\n}\n\n.order_price {\n  font-size: 12px;\n  color: #666;\n  display: block;\n}\n\n#dialogs, #changeLogs{\n  display: none;\n}\n\n.zaoshu-icon {\n  width: auto;\n  height: 15px;\n  margin-top: -4px;\n  vertical-align: middle;\n}\n\n.guide .weui-dialog__bd {\n  line-height: 1.6;\n  max-height: 300px;\n  overflow-x: hidden;\n}\n\n.guide .weui-dialog__bd p {\n  margin-bottom: 1em;\n}\n\n.testbox p {\n  text-align: left;\n}\n\n.new_price {\n  font-size: 14px;\n  color: #333;\n}\n\n.new_price.up {\n  color: #690;\n}\n\n.new_price.down {\n  color: #ea2222;\n}\n\n.time {\n  text-align: right;\n}\n\n.alipay_pay {\n  display: none;\n}\n\n.alipay_pay img {\n  padding: 24px;\n  background: #fff;\n}\n\n.weui-dialog__ft a {\n  cursor: pointer;\n}\n\n.weui-dialog .segmented-control {\n  width: 80%;\n  margin: 0 auto;\n  border: 1px solid #eee;\n  border-radius: 4px;\n}\n\n.segmented-control {\n  display: table;\n  width: 100%;\n  margin: 2em 0;\n  padding: 0;\n  background: #fff;\n}\n\n.segmented-control__item:first-child {\n  float: left;\n}\n\n.segmented-control__item:last-child {\n  float: right;\n}\n\n.segmented-control__item:first-child .segmented-control__label {\n  border-radius: 4px 0 0 4px;\n}\n\n.segmented-control__item:last-child .segmented-control__label {\n  border-radius: 0 4px 4px 0;\n}\n\n.segmented-control__item {\n  width: 49.5%;\n  display: inline-block;\n  margin: 0;\n  padding: 0;\n  list-style-type: none;\n}\n\n.segmented-control__input {\n  position: absolute;\n  visibility: hidden;\n}\n\n.segmented-control__label {\n  display: block;\n  margin: 0 -1px -1px 0;\n  /* -1px margin removes double-thickness borders between items */\n  padding: .45em .25em;\n  font: 14px/1.5 sans-serif;\n  text-align: center;\n  cursor: pointer;\n}\n\n.segmented-control__label:hover {\n  background: #fafafa;\n}\n\n.checked .segmented-control__label {\n  background: #1aad19;\n  color: #fff;\n}\n\n.auto_login {\n  font-size: 14px;\n  margin: 10px 5px;\n}\n\n.weui-navbar__item {\n  padding: 7px 0;\n  cursor: pointer;\n  font-size: 15px;\n  color: var(--weui-TAG-TEXT-BLACK);\n}\n\n.recommendedLink p {\n  text-align: center;\n  margin-top: 5px;\n}\n\n.iframe-loading {\n  z-index: 0 !important;\n}\n\n.js-close {\n  position: absolute;\n  right: 10px;\n  top: 1px;\n  padding: 4px;\n  font-size: 18px;\n  cursor: pointer;\n}\n\n.settings_box {\n  overflow-y: auto;\n  overflow-x: hidden;\n  height: 515px;\n}\n\n.settings .page__desc {\n  font-size: 12px;\n  height: 31px;\n  line-height: 18px;\n  color: #666;\n}\n\n.settings .weui-tab {\n  height: auto;\n}\n\n.tips .page__desc {\n  padding: 2px 5px;\n}\n\n.bottom {\n  height: 44px;\n  font-size: 12px;\n  position: relative;\n}\n\nimg.weui-tabbar__icon {\n  width: 20px;\n  height: 20px;\n  cursor: pointer;\n}\n\n.changelogs {\n  font-size: 14px;\n  padding: 10px;\n  text-align: left;\n  line-height: 2.2;\n  max-height: 300px;\n  overflow-y: auto;\n}\n\n.loginNotice {\n  font-size: 14px;\n  padding: 10px 15px;\n  text-align: left;\n  line-height: 2.2;\n  max-height: 300px;\n  color: #5a5a5a;\n  overflow-y: auto;\n}\n\n.loginNotice b {\n  color: #2b902f;\n}\n\n#loginNotice .title {\n  background: #fbf3a5;\n  color: #ce7f66;\n  padding-top: 0.8em;\n}\n\n#loginNotice a {\n  color: #126700;\n}\n\n#loginNotice a:hover {\n  color: #0c4600;\n}\n\n#loginNotice a.failed {\n  color: #de4545;\n}\n\n#loginNotice.state-alive .title {\n  background: #b9e684ad;\n  color: #2b902f;\n  padding-top: 0.8em;\n}\n\n#faqDialags .weui-dialog, #feedbackDialags .weui-dialog {\n  background-color: #f8f8f8;\n  height: 460px;\n}\n\n#specialEventDialags iframe, #faqDialags iframe, #feedbackDialags iframe {\n  width: 90%;\n  height: 450px;\n}\n\n#feedbackResult, #wechatDialags, #feedbackDialags, #faqDialags, #specialEventDialags, #loginNotice, #listenAudio {\n  display: none;\n}\n\n.contents-box {\n  margin-top: 0;\n}\n\n.listenVoice {\n  text-align: left;\n}\n\n.messages-header {\n  display: flex;\n  border-bottom: 1px solid #ebeef5;\n  height: 33px;\n  padding-top: 0px;\n  position: fixed;\n  width: 54%;\n  background: #fafafa;\n  z-index: 10;\n}\n\n#order.weui-cells:after {\n  border-bottom: none;\n}\n\n.messages-tab {\n  position: relative;\n  flex: 1;\n  height: 48px;\n  cursor: pointer;\n}\n\n.messages-tab span {\n  width: 22px;\n  height: 22px;\n  background: #ccc;\n  display: block;\n  margin: 0 auto;\n}\n\n.messages-tab.selectedTab span {\n  background: #4bc2ff;\n}\n\n.messages-tab span.notice {\n  -webkit-mask: url(../image/notice.svg) no-repeat center;\n  mask: url(../image/notice.svg) no-repeat center;\n  -webkit-mask-size: 20px;\n  mask-size: 20px;\n}\n\n.messages-tab span.coupon {\n  -webkit-mask: url(../image/coupon.svg) no-repeat center;\n  mask: url(../image/coupon.svg) no-repeat center;\n  -webkit-mask-size: 22px;\n  mask-size: 22px;\n}\n\n.messages-tab span.checkin {\n  -webkit-mask: url(../image/checkin.svg) no-repeat center;\n  mask: url(../image/checkin.svg) no-repeat center;\n  -webkit-mask-size: 20px;\n  mask-size: 20px;\n}\n\n.message-items {\n  margin-top: 20px;\n}\n\n.message-items .weui-media-box {\n  padding: 5px 15px;\n  border-bottom: 1px solid #ebeef5;\n}\n\n.Button--link, .Button--plain {\n  height: auto;\n  padding: 0;\n  line-height: inherit;\n  border: none;\n  border-radius: 0;\n}\n\n.messages-tabIcon {\n  fill: #c2cfde;\n}\n\n.selectedTab .messages-tabIcon {\n  fill: #0f88eb;\n}\n\nbutton.Button.messages-tab.Button--plain:focus {\n  outline: none;\n}\n\n.selectedTab {\n  background: #f5f5f5;\n  border: 1px solid #e6e6e6;\n  border-bottom: none;\n  border-top: none;\n  background-image: linear-gradient(0deg, #ffffff, #e6e6e64a);\n}\n\n.message i {\n  padding-right: 5px;\n}\n\n.message .checkin_notice {\n  background: url(../image/mileage.png) no-repeat;\n  width: 20px;\n  height: 20px;\n  background-size: 20px;\n  display: inline-block;\n  margin-bottom: -3px;\n}\n\n.message .checkin_notice.coin {\n  background-image: url(../image/coin.png);\n}\n\n.message .notice {\n  background: url(../image/notice.png) no-repeat;\n  width: 20px;\n  height: 20px;\n  background-size: 20px;\n  display: inline-block;\n  margin-bottom: -3px;\n}\n\n.coupon-box {\n  position: relative;\n  height: 50px;\n  border: 1px solid #f2f2f2;\n  background: #fff;\n  display: inline-block;\n  display: block;\n  padding: 10px;\n}\n\n.coupon-box .price {\n  padding: 1px 5px;\n  color: #f23030;\n  font-size: 15px;\n  background: #fff4ec;\n  display: inline-block;\n}\n\n.coupon-box a {\n  font-size: 14px;\n  color: #555;\n  padding: 4px;\n}\n\n.reward {\n  cursor: pointer;\n  display: block;\n  color: #d29737;\n}\n\n.reward h4 {\n  padding-top: 10px;\n  font-size: 22px;\n  color: #4e4c4c;\n}\n\n.reward .qrcode {\n  width: 210px;\n  padding: 10px;\n}\n\n.reward .switch-tips {\n  font-size: 14px;\n  color: #ccc;\n}\n\n.other_actions .tips {\n  color: #ccc;\n}\n\n.switch {\n  cursor: pointer;\n  color: #f54e4d;\n}\n\n.switch p {\n  margin-top: -10px;\n}\n\n.switch .icon {\n  margin-bottom: -5px;\n}\n\n#unreadCount {\n  display: none;\n}\n\n.alipay_pay .redpack img {\n  padding: 10px;\n}\n\n.switch-paymethod i {\n  font-size: 21px;\n  margin-top: -4px;\n}\n\n.weui-cell__bd .weui-icon-info-circle {\n  margin-top: -4px;\n}\n\n#listenAudio .weui-cells {\n  margin-bottom: .8em;\n}\n\n#listenAudio .weui-cell_access {\n  cursor: pointer;\n}\n\n#changeLogs b {\n  color: #4CAF50;\n}\n\n.text-tips {\n  font-size: 12px;\n  text-align: center;\n  color: #666;\n  padding-top: 5px;\n  padding-bottom: 10px;\n}\n\n.recommendServices {\n  text-align: center;\n}\n\n.recommendServices .el-tag {\n  margin-right: 2px;\n}\n\n.openMobilePage {\n  cursor: pointer;\n}\n\n.el-tag {\n  background-color: rgba(64, 158, 255, .1);\n  display: inline-block;\n  padding: 0 10px;\n  height: 32px;\n  line-height: 30px;\n  font-size: 12px;\n  color: #409eff;\n  border-radius: 4px;\n  box-sizing: border-box;\n  border: 1px solid rgba(64, 158, 255, .2);\n  white-space: nowrap;\n}\n\n.el-tag a {\n  color: #2196F3;\n}\n\n.el-tag--success {\n  background-color: rgba(103, 194, 58, .1);\n  border-color: rgba(103, 194, 58, .2);\n  color: #67c23a;\n}\n\n.el-tag--success a {\n  color: #67c23a;\n}\n\n.el-tag--warning {\n  background-color: rgba(230, 162, 60, .1);\n  border-color: rgba(230, 162, 60, .2);\n  color: #e6a23c;\n}\n\n.el-tag--warning a {\n  color: #e6a23c;\n}\n\n.el-tag--danger {\n  background-color: hsla(0, 87%, 69%, .1);\n  border-color: hsla(0, 87%, 69%, .2);\n  color: #f56c6c;\n}\n\n.el-tag--danger a {\n  color: #ff1d1d;\n}\n\n.bottom-box {\n  height: 55px;\n  border-top: #d4d4d4;\n  background: #f7f7fa0a;\n  position: relative;\n}\n\n.bottom-box .avatar {\n  width: 30px;\n  position: absolute;\n  left: 10px;\n  bottom: 11px;\n  height: 30px;\n}\n\n.bottom-box .login-state {\n  -webkit-mask: url(../image/avatar.svg) no-repeat center;\n  -webkit-mask-size: 30px;\n  mask: url(../image/avatar.svg) no-repeat center;\n  mask-size: 30px;\n  width: 30px;\n  height: 30px;\n  color: #dcdcdc;\n  display: inline-block;\n  cursor: pointer;\n  background-color: #cecece;\n}\n\n.bottom-box .login-state.alive {\n  background-color: #41bd2a;\n}\n\n.bottom-box .login-state.failed {\n  background-color: #f56c6c;\n}\n\n.bottom-box .login-state.warning {\n  background-color: #f7aa4d;\n}\n\n.bottom-box .links {\n  right: 10px;\n  position: absolute;\n  bottom: 10px;\n}\n\n.links .text-tips {\n  color: #bbb;\n  padding-bottom: 5px;\n}\n\n.links .el-tag {\n  padding: 0 8px;\n}\n\n.tips .weui-btn {\n  display: none;\n}\n\n.tips a.weui-btn_primary.weui-btn:hover {\n  color: #ffffffd6;\n}\n\n.showChangeLog {\n  cursor: pointer;\n}\n\n.offline-icon {\n  -webkit-mask: url(../image/offline.svg) no-repeat center;\n  -webkit-mask-size: 22px;\n  mask: url(../image/offline.svg) no-repeat center;\n  mask-size: 22px;\n  width: 22px;\n  height: 22px;\n  display: inline-block;\n  background-color: #666;\n  padding-right: 5px;\n  margin-bottom: -4px;\n}\n\n.online-icon {\n  -webkit-mask: url(../image/online.svg) no-repeat center;\n  -webkit-mask-size: 22px;\n  mask: url(../image/online.svg) no-repeat center;\n  mask-size: 22px;\n  width: 22px;\n  height: 22px;\n  background-color: #2b902f;\n  padding-right: 5px;\n  margin-bottom: -4px;\n  display: none;\n}\n\n.request-permissions-icon.weui-icon-warn {\n  color: #FFC107;\n  font-size: 22px;\n  cursor: pointer;\n}\n\n.state-alive .online-icon {\n  display: inline-block;\n}\n\n.state-alive .offline-icon {\n  display: none;\n}\n\n.el-tag--plus {\n  border-color: #f7aa4d;\n  background-color: #f9d2a3;\n}\n\n.el-tag--plus a {\n  color: #da8d00;\n}\n\n.settings .weui-cells_form {\n  margin-top: .8em;\n}\n\n.help_btns .el-tag a {\n  font-size: 14px;\n  padding: 12px;\n}\n\n.text-tips.version {\n  padding-bottom: 0;\n  cursor: pointer;\n}\n\n.loginNotice .detail {\n  display: none;\n  padding-top: 20px;\n}\n\n.loginNotice .detail h3 {\n  text-align: center;\n}\n\n.unknown .status-icon {\n  background-color: #ccc;\n}\n\n.alive .status-icon {\n  background-color: #289e2d;\n}\n\n.alive .status-text {\n  color: #289e2d;\n}\n\n.alive .weui-cell {\n  background: #e6ffcad4;\n}\n\n.alive .weui-cell:hover {\n  background: #d8f9b4;\n}\n\n.failed .weui-cell {\n  background: #ffd3d3b5;\n}\n\n.failed .weui-cell:hover {\n  background: #ffd9d9;\n}\n\n.failed .status-text {\n  color: #de4545;\n}\n\n#login i {\n  margin-top: -3px;\n}\n\n#know_more {\n  text-align: center;\n  padding-top: 10px;\n  color: #999;\n  cursor: pointer;\n}\n\n.update .weui-dialog .weui-dialog__bd {\n  white-space: pre-line;\n  text-align: left;\n  padding-top: 1em;\n}\n\n.update .dismiss {\n  cursor: pointer;\n  float: right;\n  margin-top: -13px;\n  padding: 5px 10px;\n  font-size: 22px;\n  margin-right: -7px;\n}\n\n.showApplyAlipayCode {\n  cursor: pointer;\n  margin-top: 10px;\n  color: #10aeff;\n}\n\n.apply-alipay-code .weui-dialog {\n  border: 5px solid #10aeff;\n  min-height: 400px;\n}\n\n.apply-alipay-code .weui-dialog__title {\n  color: #10aeff;\n}\n\n.reward-tips {\n  font-size: 12px;\n}\n\n.new-version {\n  margin-left: 5px;\n  margin-right: 5px;\n  margin-top: -2px;\n  padding: .2em .5em;\n}\n\n\n@media screen and (min-width: 352px) {\n  .weui-dialog {\n      width: 440px;\n  }\n}"
  },
  {
    "path": "static/style/start.css",
    "content": ".start{\n    width: 640px;\n    margin: 0 auto;\n}\n\n.page, body {\n    background-color: var(--weui-BG-0);\n}\n\n.find-coupon{\n    width: 100%;\n    border: 1px solid #ccc;\n    margin: 12px 0;\n}"
  },
  {
    "path": "static/style/style.css",
    "content": "#teaclub {\n  min-height: 80px;\n  background: #f1fde347;\n  padding: 10px;\n  margin-bottom: 1em;\n}\n\n#teaclub .information-from{\n  font-size: 12px;\n  color: #ccc;\n  height: 12px;\n  display: block;\n  text-align: right;\n  padding-top: 4px;\n  padding-bottom: 4px;\n}\n\n#teaclub dt.metatit{\n  text-align: left;\n  float: left;\n  width: 66px;\n}\n.tb-wrap-newshop #teaclub dt.metatit{\n  width: 60px;\n}\n\n#teaclub .prop dd{\n  width: 420px;\n  float: left;\n  position: relative;\n  overflow: visible;\n  height: auto;\n  z-index: 1;\n}\n\n.tb-wrap-newshop #teaclub .prop dd{\n  width: 400px;\n}\n\n#teaclub .clear:after {\n  content: '\\20';\n  display: block;\n  height: 0;\n  clear: both;\n}\n\n#Coupon-box{\n  margin-bottom: 1em;\n}\n\na.teaclub-coupon:hover {\n  text-decoration: none;\n}\n\n#teaclub .loading, #teaclub .coupon-not-found {\n  background: #fff;\n  font-size: 16px;\n  text-align: center;\n  color: #ccc;\n}\n\n#teaclub .loading img, #teaclub .coupon-not-found img {\n  width: 80px;\n  vertical-align: middle;\n}\n\n\n\n#teaclub .qrcode{\n  width: 60px;\n  display: block;\n  float: left;\n  padding: 5px;\n}\n\n.teaclub-discount {\n  padding: 10px;\n  background-color: #FFF2E8;\n}\n\n.coupon-bonus-item {\n  width: 100%;\n  background-color: #ffe2e0;\n  border-radius: 6px;\n  display: block;\n  max-width: 400px;\n}\n\n.coupon-bonus-item .coupon-item-left {\n  width: 69%;\n  color: #fff;\n  background-color: #de1d3d;\n  border-radius: 6px 0 0 6px;\n  display: inline-block;\n}\n\n.coupon-bonus-item .coupon-item-right {\n  text-align: right;\n  padding-left: 7px;\n  margin-top: 7px;\n  color: #df4c47;\n  display: inline-block;\n}\n\n.coupon-bonus-item .coupon-item-rmb {\n  font-family: arial;\n  font-size: 14px;\n  line-height: 18px;\n  text-align: right;\n  padding-right: 12px;\n  margin-top: 14px;\n}\n\n.coupon-bonus-item .coupon-item-surplus {\n  font-size: 12px;\n  line-height: 18px;\n  text-align: right;\n  padding-right: 12px;\n  margin-top: 6px;\n}\n\n.coupon-bonus-item .coupon-item-rmb .rmb {\n  font-family: tahoma;\n  font-size: 28px;\n  line-height: 18px;\n}\n\n#J_OtherDiscount .coupon-bonus-item {\n  line-height: 1;\n  margin-top: 20px;\n}\n\n#teaclub .PDD-card {\n  -webkit-box-align: stretch;\n  -ms-flex-align: stretch;\n  align-items: stretch;\n  overflow: hidden;\n  position: relative;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  min-height: 128px;\n  max-width: 400px;\n  border-radius: 6px;\n  background-color: #f5f5f5;\n  text-decoration: none;\n  margin-bottom: 0.5em;\n}\n\n#teaclub .PDD-cardContainer {\n  -webkit-box-align: stretch;\n  -ms-flex-align: stretch;\n  align-items: stretch;\n  padding: 14px;\n  box-sizing: border-box;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  position: relative;\n  width: 100%;\n  z-index: 2;\n}\n\n#teaclub .PDD-imageContainer {\n  border-radius: 0px;\n  height: 100px;\n  width: 100px;\n  flex-shrink: 0;\n  overflow: hidden;\n  position: relative;\n}\n\n#teaclub .PDD-image {\n  height: 100%;\n  width: 100%;\n}\n\n#teaclub .PDD-qrcode {\n  position: absolute;\n}\n\n#teaclub .PDD-cardContainer:hover .PDD-qrcode {\n  z-index: 10;\n}\n\n#teaclub .PDD-info {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-direction: normal;\n  -ms-flex-direction: column;\n  flex-direction: column;\n  -webkit-box-flex: 1;\n  -ms-flex-positive: 1;\n  flex-grow: 1;\n  margin-left: 12px;\n}\n\n#teaclub .PDD-titleText {\n  line-height: 20px;\n  max-height: 40px;\n  color: #1a1a1a;\n  font-size: 16px;\n  line-height: 19px;\n  font-weight: 600;\n  font-synthesis: style;\n  display: -webkit-box;\n  -webkit-line-clamp: 2;\n  -webkit-box-orient: vertical;\n  overflow: hidden;\n}\n\n#teaclub .PDD-tool {\n  margin-top: auto;\n  -webkit-box-align: end;\n  -ms-flex-align: end;\n  align-items: flex-end;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n\n#teaclub .PDD-toolLeft {\n  margin-right: auto;\n}\n\n#teaclub .PDD-price {\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  color: #FF0036;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  font-size: 16px;\n  font-weight: 500;\n  line-height: 18px;\n  margin-right: auto;\n}\n\n#teaclub .PDD-button--plain.PDD-button--orange {\n  color: #FF0036;\n}\n\n#teaclub .PDD-button--plain {\n  -ms-flex-item-align: start;\n  align-self: flex-start;\n  font-size: 13px;\n  height: 18px;\n  font-weight: 600;\n  font-synthesis: style;\n}\n\n#teaclub .PDD-button {\n  -webkit-box-align: center;\n  -ms-flex-align: center;\n  align-items: center;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex-negative: 0;\n  flex-shrink: 0;\n  -webkit-box-pack: center;\n  -ms-flex-pack: center;\n  justify-content: center;\n}\n\n#teaclub-slides .slideshow-container {\n  max-width: 1000px;\n  position: relative;\n  margin: auto;\n}\n\n#teaclub-slides .teaClubSlides {\n  display: none;\n}\n\n/* Next & previous buttons */\n#teaclub .prev, #teaclub .next {\n  cursor: pointer;\n  position: absolute;\n  top: 50%;\n  width: auto;\n  margin-top: -22px;\n  padding: 16px;\n  color: white;\n  font-weight: bold;\n  font-size: 18px;\n  transition: 0.6s ease;\n  border-radius: 0 3px 3px 0;\n  user-select: none;\n}\n\n/* Position the \"next button\" to the right */\n#teaclub .next {\n  right: 0;\n  border-radius: 3px 0 0 3px;\n}\n\n/* On hover, add a black background color with a little bit see-through */\n#teaclub .prev:hover, #teaclub .next:hover {\n  background-color: rgba(0,0,0,0.8);\n}\n\n/* Caption text */\n#teaclub .text {\n  color: #f2f2f2;\n  font-size: 15px;\n  padding: 8px 12px;\n  position: absolute;\n  bottom: 8px;\n  width: 100%;\n  text-align: center;\n}\n\n/* Number text (1/3 etc) */\n#teaclub .number-text {\n  color: #f2f2f2;\n  font-size: 12px;\n  padding: 8px 12px;\n  position: absolute;\n  top: 0;\n}\n\n/* The dots/bullets/indicators */\n#teaclub .dot {\n  cursor: pointer;\n  height: 15px;\n  width: 15px;\n  margin: 0 2px;\n  background-color: #bbb;\n  border-radius: 50%;\n  display: inline-block;\n  transition: background-color 0.6s ease;\n}\n\n#teaclub .active,#teaclub .dot:hover {\n  background-color: #717171;\n}\n\n/* Fading animation */\n#teaclub .fade {\n  -webkit-animation-name: fade;\n  -webkit-animation-duration: 1.5s;\n  animation-name: fade;\n  animation-duration: 1.5s;\n}\n\n@-webkit-keyframes fade {\n  from {opacity: .4}\n  to {opacity: 1}\n}\n\n@keyframes fade {\n  from {opacity: .4}\n  to {opacity: 1}\n}"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require('path');\nconst VueLoaderPlugin = require('vue-loader/lib/plugin');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\nconst CopyPlugin = require('copy-webpack-plugin');\nconst { EnvironmentPlugin } = require('webpack')\n\nfunction modifyManifest(buffer) {\n  let manifest = JSON.parse(buffer.toString());\n\n  // make any modifications you like, such as\n  if (process.env.VERSION) {\n    manifest.version = process.env.VERSION;\n  }\n\n  // pretty print to JSON with two spaces\n  manifest_JSON = JSON.stringify(manifest, null, 2);\n  return manifest_JSON;\n}\n\nmodule.exports = {\n  mode: process.env.NODE_ENV || 'development',\n  entry: {\n    background: './src/background.js',\n    content_script: './src/content_script.js',\n    start: './src/start.js',\n    popup: './src/popup.js'\n  },\n  output: {\n    filename: 'static/[name].js',\n    path: path.resolve(__dirname, 'dist')\n  },\n  resolve: {\n    alias: {\n      vue: 'vue/dist/vue.runtime.esm.js'\n    }\n  },\n  node: {\n    fs: 'empty'\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(png|jpg|gif)$/i,\n        use: [\n          {\n            loader: 'url-loader',\n            loader: 'file-loader',\n            options: {\n              limit: 8192,\n              outputPath: 'images',\n              esModule: false\n            },\n          },\n        ],\n      },\n      {\n        test: /\\.svg$/,\n        use: [\n          'svg-url-loader',\n        ]\n      },\n      {\n        test: /\\.less$/,\n        use: [\n          'vue-style-loader',\n          'css-loader',\n          'less-loader'\n        ]\n      },\n      {\n        test: /\\.css$/i,\n        use: ['style-loader', 'css-loader'],\n      },\n      {\n        test: /\\.vue$/,\n        loader: 'vue-loader'\n      }\n    ]\n  },\n  plugins: [\n    new CleanWebpackPlugin(),\n    new CopyPlugin([\n      {\n        from: \"public/manifest.json\",\n        to: \"./manifest.json\",\n        transform(content, path) {\n          return modifyManifest(content)\n        }\n      },\n      { from: 'public', to: '.' },\n      {\n        from: 'static/image/icon',\n        to: 'static/image'\n      },\n      {\n        from: 'node_modules/@sunoj/touchemulator/touch-emulator.js',\n        to: 'static'\n      },\n      {\n        from: 'node_modules/zepto/dist/zepto.min.js',\n        to: 'static'\n      }\n    ]),\n    new VueLoaderPlugin(),\n    new EnvironmentPlugin({\n      NODE_ENV: 'development',\n      BROWSER: 'chrome',\n      VERSION: '0.1.1',\n      BUILDID: 0\n    })\n  ]\n};"
  }
]