Repository: sunoj/teaclub Branch: master Commit: 52857fa40390 Files: 28 Total size: 140.0 KB Directory structure: gitextract_q6d88nwp/ ├── .babelrc ├── .vscode/ │ └── settings.json ├── package.json ├── public/ │ ├── background.html │ ├── manifest.json │ ├── popup.html │ └── start.html ├── readme.md ├── src/ │ ├── account.js │ ├── background.js │ ├── components/ │ │ ├── app.vue │ │ ├── discounts.vue │ │ ├── events.vue │ │ ├── guide.vue │ │ ├── links.vue │ │ ├── loading.vue │ │ ├── popup.vue │ │ └── report.vue │ ├── content_script.js │ ├── popup.js │ ├── start.js │ ├── tasks.js │ ├── utils.js │ └── variables.js ├── static/ │ └── style/ │ ├── popup.css │ ├── start.css │ └── style.css └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ ["env", { "targets": { "browsers": ["last 1 Chrome version"] } }] ], "plugins": [ ["transform-runtime", { "polyfill": false, "regenerator": true }] ] } ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "Lazyload", "fliggy", "metatit", "tmall" ] } ================================================ FILE: package.json ================================================ { "name": "teaclub", "version": "0.0.1", "author": "Ming", "private": true, "scripts": { "start": "npx webpack --config webpack.config.js", "build": "NODE_ENV=production yarn start", "dev": "webpack --watch" }, "dependencies": { "@sunoj/touchemulator": "^0.0.1", "babel-plugin-transform-runtime": "^6.23.0", "dexie": "^2.0.4", "gulp": "^4.0.0", "gulp-clean-css": "^3.9.0", "gulp-concat": "^2.6.1", "gulp-preprocess": "^2.0.0", "gulp-replace": "^0.6.1", "gulp-watch": "^4.3.11", "hooper": "^0.1.5", "jobs": "^0.0.4", "jquery": "3.5", "lodash": "^4.17.19", "logline": "^1.1.2", "luxon": "^1.4.3", "microtip": "^0.2.2", "parcel-bundler": "^1.12.3", "qrcode-svg": "^1.1.0", "vue": "^2.6.11", "vue-infinite-loading": "^2.4.5", "vue-lazyload": "^1.3.3", "weui": "^2.3.0", "weui.js": "^1.2.1", "zepto": "^1.2.0" }, "alias": { "vue": "./node_modules/vue/dist/vue.common.js" }, "devDependencies": { "@vue/component-compiler-utils": "^2.6.0", "babel-core": "^6.26.3", "babel-preset-env": "^1.7.0", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^5.1.1", "css-loader": "^3.5.3", "file-loader": "^6.0.0", "html-webpack-plugin": "^4.3.0", "less": "^3.11.1", "less-loader": "^6.0.0", "parcel-plugin-static-files-copy": "^2.3.1", "style-loader": "^1.2.1", "svg-inline-loader": "^0.8.2", "svg-url-loader": "^5.0.0", "url-loader": "^4.1.0", "vue-loader": "^15.9.1", "vue-template-compiler": "^2.6.11", "webpack": "^4.43.0", "webpack-cli": "^3.3.11" } } ================================================ FILE: public/background.html ================================================ ================================================ FILE: public/manifest.json ================================================ { "manifest_version": 2, "name": "茶友会 - 淘宝查券助手", "short_name": "茶友会", "description": "茶友会是自动为你查找淘宝优惠券,自动签到领飞猪里程的多功能购物助手", "version": "0.3.1", "background": { "page": "background.html", "persistent": true }, "browser_action": { "default_icon": "static/image/icon.png", "default_popup": "popup.html" }, "content_scripts": [ { "matches": ["*://*.taobao.com/*", "*://*.tmall.com/*", "*://*.fliggy.com/*"], "exclude_matches": ["*://ratewrite.tmall.com/*", "*://rate.taobao.com/*", "*://passport.taobao.com/*", "*://buy.taobao.com/*", "*://buy.tmall.com/*"], "js": [ "static/zepto.min.js", "static/content_script.js" ], "run_at": "document_end", "all_frames": true } ], "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';", "icons": { "16": "static/image/icon@16.png", "48": "static/image/icon@48.png", "128": "static/image/icon@128.png" }, "web_accessible_resources": [ "static/touch-emulator.js" ], "permissions": [ "*://*.taobao.com/*", "*://*.tmall.com/*", "*://*.fliggy.com/*", "webRequest", "webRequestBlocking", "alarms", "contextMenus", "notifications" ] } ================================================ FILE: public/popup.html ================================================ 茶友会 ================================================ FILE: public/start.html ================================================ 茶友会安装成功!

茶友会安装成功

我可以自动为你自动签到领飞猪里程、自动查找渠道优惠券

自动找券演示
================================================ FILE: readme.md ================================================ # 茶友会 - 淘宝查券助手 茶友会是自动为你查找淘宝优惠券,自动签到领飞猪里程的多功能购物助手。 **强烈推荐使用 Chrome 商店安装**(这样才能获得自动更新): Chrome
  web store 或者直接下载的 CRX文件手动安装(非常不建议) 下载 CRX 文件 *此方法通常只适用于 Chromium 内核的国产浏览器,因为 Chrome 出于安全原因已禁止通过除 Chrome 官方商店以外的其他渠道安装拓展。* ## 主要功能 * 自动查询当前浏览商品的渠道优惠券 * 自动签到领取飞猪里程 * 自动找拼多多低价同款 ## 演示 ![自动查券](https://jjbcdn.zaoshu.so/teaclub/find-coupon.gif) ## 重要提示 1. 茶友会并非开源软件,不许可您以任何形式进行再发行,请仔细阅读[#协议和授权](https://github.com/sunoj/teaclub#%E5%8D%8F%E8%AE%AE%E5%92%8C%E6%8E%88%E6%9D%83)。 2. 当前仓库是插件源代码,无法直接安装,如需安装请自行参考 [#如何开发](https://github.com/sunoj/teaclub#%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91) 编译。 3. 茶友会绝对不会在任何情况下强行劫持任何网页的访问,如果发现类似问题请善用 Google 搜索并使用二分法停用插件排除,同时考虑运营商劫持的可能性。或者,为了防止茶友会的影响亦可直接卸载茶友会。故不再回复类似的 Issue。详情参考:[#安全提示](https://github.com/sunoj/teaclub#%E5%AE%89%E5%85%A8%E6%8F%90%E7%A4%BA) ## 如何开发 * 安装依赖 > yarn * 开始开发 > BUILDID=1 VERSION=1.1.1 BROWSER=chrome yarn build `主要作用就是合并压缩代码,质疑代码和市场版本不一致,请先自行打包一下再对比` ## 安全提示 茶友会不会在任何情况下强行劫持访问、插入恶意代码、上传隐私信息或利用你的电脑挖矿。 若你发现任何类似问题,请首先确保你使用的是商店版本,不建议在任何情况下使用第三方提供的安装包。 ## 系统支持 目前茶友会对 Windows 和 Mac 平台的 Chrome 有较好的支持。 Ubuntu 有明确的兼容问题,由于作者不拥有任何 Ubuntu 设备,因此暂时无法解决。 ## 协议和授权 茶友会并非一个开源软件,作者保留全部的权利。 公开源代码的目的是为了让使用者能够审计代码,但是你仍然可以就以下方式合法的使用本项目的全部代码和资源: 1. 个人使用 2. 以学习目的使用全部或部分代码 但你不可以: 1. 将本项目的部分或全部代码和资源进行任何形式的再发行(尤其是上传到 Chrome 商店) 2. 利用本项目的部分或全部代码和资源进行任何商业行为 ## 贡献代码 茶友会并非一个开源项目,也不是社区共同创造,其全部功能由作者独立完成。 如果你愿意放弃所有权利,并将权利无条件转让给茶友会作者,欢迎您贡献代码。 ## 提交反馈 欢迎提交 issue,请写清楚遇到问题的原因,浏览器和操作系统环境,重现的流程。 任何反馈问题的 issue 均需按照模板格式填写,否则将被直接关闭。 如果有开发能力,建议在本地调试出出错的代码。 ## 联系作者 请发邮件至:`ming@tiny.group` 请勿发送功能咨询邮件,将不会收到回复。相关功能细节请自行了解。 ## 相关项目

京价保

================================================ FILE: src/account.js ================================================ import { getSetting } from './utils' export const getLoginState = function () { let loginState = { pc: getSetting('login-state_pc', { state: "unknown" }), m: getSetting('login-state_m', { state: "unknown" }), class: "unknown" } // 处理登录状态 if (loginState.pc.state == 'alive' || loginState.m.state == 'alive') { loginState.class = "alive" } if (loginState.pc.state == 'failed' || loginState.m.state == 'failed') { loginState.class = "failed" } if (loginState.pc.time && loginState.m.time) { if (new Date(loginState.pc.time) > new Date(loginState.m.time)) { loginState.class = loginState.pc.state } else { loginState.class = loginState.m.state } } return loginState } ================================================ FILE: src/background.js ================================================ $ = window.$ = window.jQuery = require('jquery') import * as _ from "lodash" import Logline from 'logline' import Dexie from 'dexie'; import {DateTime} from 'luxon' import {tasks, mapFrequency, getTasks, getTask} from './tasks' import {rand, getSetting, saveSetting} from './utils' import {getLoginState} from './account' const db = new Dexie("messages"); db.version(1).stores({ messages: "++id,type,timestamp" }); async function newMessage(messageId, data) { let order = await db.messages.where('id').equals(messageId).toArray(); if (order && order.length > 0) return await db.messages.update(messageId, data) let messageInfo = Object.assign(data, { id: messageId, }) return await db.messages.add(messageInfo); } async function updateMessages() { // 最多只展示最近 30 天的消息 let last30Day = Date.now() - 60*60*1000*24*30; let messages = await db.messages.where('timestamp').above(last30Day).reverse().sortBy('timestamp') saveSetting('messages', messages) chrome.runtime.sendMessage({ action: "messages_updated", messages: messages }); } Logline.using(Logline.PROTOCOL.INDEXEDDB) var logger = {} var mobileUAType = getSetting('uaType', 1) // 设置默认频率 _.forEach(tasks, (task) => { let frequency = getSetting(`task-${task.id}_frequency`) if (!frequency) { localStorage.setItem(`task-${task.id}_frequency`, task.frequency) } }) // This is to remove X-Frame-Options header, if present chrome.webRequest.onHeadersReceived.addListener( function(info) { var headers = info.responseHeaders; for (var i=headers.length-1; i>=0; --i) { var header = headers[i].name.toLowerCase(); if (header == 'x-frame-options' || header == 'frame-options') { headers.splice(i, 1); // Remove header } } return {responseHeaders: headers}; }, { urls: ['*://*.taobao.com/*', '*://*.tmall.com/*', '*://*.fliggy.com/*'], types: ['sub_frame'] }, ['blocking', 'responseHeaders'] ); chrome.runtime.onInstalled.addListener(function (object) { let installed = localStorage.getItem('installed') let uaType = localStorage.getItem('uaType') if (installed) { if (!uaType) { localStorage.setItem('uaType', 1); } localStorage.setItem('oldUser', 'Y') console.log("已经安装") } else { localStorage.setItem('installed', 'Y'); localStorage.setItem('uaType', rand(3)); chrome.tabs.create({url: "/start.html"}, function (tab) { console.log("茶友会安装成功!"); }); } }); var popularPhoneUA = [ '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)', '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)', '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)', '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)' ]; chrome.webRequest.onBeforeSendHeaders.addListener( function (details) { for (var i = 0; i < details.requestHeaders.length; ++i) { if (details.requestHeaders[i].name === 'User-Agent') { details.requestHeaders[i].value = popularPhoneUA[mobileUAType]; break; } } return { requestHeaders: details.requestHeaders }; }, { urls: [ "*://*.m.taobao.com/*", ] }, ['blocking', 'requestHeaders']); // 判断浏览器 try { browser.runtime.getBrowserInfo().then(function (browserInfo) { localStorage.setItem('browserName', browserInfo.name); }) } catch (error) {} chrome.alarms.onAlarm.addListener(function( alarm ) { log('background', "onAlarm", alarm) let taskId = alarm.name.split('_')[1] switch(true){ // 计划任务 case alarm.name.startsWith('runScheduleJob'): runJob(taskId, true) break; // 定时任务 case alarm.name.startsWith('runJob'): runJob(taskId) break; // 周期运行(10分钟) case alarm.name == 'cycleTask': findJobs() runJob() updateIcon() break; case alarm.name.startsWith('clearIframe'): resetIframe(taskId || 'iframe') break; case alarm.name.startsWith('destroyIframe'): $("#" + taskId).remove(); break; case alarm.name.startsWith('closeTab'): try { chrome.tabs.get(taskId, (tab) => { if (tab) { chrome.tabs.remove(tab.id) } }) } catch (e) {} break; case alarm.name == 'reload': chrome.runtime.reload() chrome.alarms.clearAll() // 保留3天内的log Logline.keep(3); break; } }) // 保存任务栈 function saveJobStack(jobStack) { jobStack = _.uniq(jobStack) localStorage.setItem('jobStack', JSON.stringify(jobStack)); } function scheduleJob(task) { let hour = DateTime.local().hour; for (var i = 0, len = task.schedule.length; i < len; i++) { let scheduledHour = task.schedule[i] if (scheduledHour > hour) { let scheduledTime = DateTime.local().set({ hour: scheduledHour, minute: rand(2) - 1, second: rand(55) }).valueOf() chrome.alarms.create('runScheduleJob_' + task.id, { when: scheduledTime }) log('background', "schedule job created", { job: task, time: scheduledHour, when: scheduledTime }) break; } } } // 寻找乔布斯 function findJobs() { let jobStack = getSetting('jobStack', []) let taskList = getTasks() taskList.forEach(function(task) { if (task.suspended) { return console.log(task.title, '由于账号未登录已暂停运行') } // 如果任务有时间安排,则把任务安排到最近的下一个时段 if (task.schedule) { return chrome.alarms.get('runScheduleJob_' + task.id, function (alarm) { if (!alarm || alarm.scheduledTime < Date.now()) { return scheduleJob(task) } else { console.log("job already scheduled ", alarm) } }) } switch(task.frequency){ case '2h': // 如果从没运行过,或者上次运行已经过去超过2小时,那么需要运行 if (!task.last_run_at || (DateTime.local() > DateTime.fromMillis(task.last_run_at).plus({ hours: 2 }))) { jobStack.push(task.id) } break; case '5h': // 如果从没运行过,或者上次运行已经过去超过5小时,那么需要运行 if (!task.last_run_at || (DateTime.local() > DateTime.fromMillis(task.last_run_at).plus({ hours: 5 }))) { jobStack.push(task.id) } break; case 'daily': // 如果从没运行过,或者上次运行不在今天,或者是签到任务但未完成 if (!task.last_run_at || !(DateTime.local().hasSame(DateTime.fromMillis(task.last_run_at), 'day')) || (task.checkin && !task.checked)) { jobStack.push(task.id) } break; default: console.log('ok, never run ', task.title) } }); saveJobStack(jobStack) } function log(type, message, details) { if (!logger[type]) { logger[type] = new Logline(type) } logger[type].info(message, details) console.log(new Date(), type, message, details) } function resetIframe(domId) { $("#" + domId).remove(); let iframeDom = ``; $('body').append(iframeDom); } function incrementUsage(task) { let year = new Date().getFullYear() let today = DateTime.local().toFormat("o") let hour = new Date().getHours() saveSetting(`temporary:usage-${task.id}_${year}d:${today}:h:${hour}`, task.usage.hour + 1) saveSetting(`temporary:usage-${task.id}_${year}d:${today}`, task.usage.daily + 1) } // 执行组织交给我的任务 function runJob(taskId, force = false) { // 不在凌晨阶段运行非强制任务 if (DateTime.local().hour < 6 && !force) { return console.log('Silent Night') } log('background', "run job", { jobId: taskId, force: force }) // 如果没有指定任务ID 就从任务栈里面找一个 if (!taskId) { let jobStack = getSetting('jobStack', []) if (jobStack && jobStack.length > 0) { taskId = jobStack.shift(); saveJobStack(jobStack) } else { return log('info', new Date(), '好像没有什么事需要我做...') } } let task = getTask(taskId) // 如果任务已暂停 if (task.pause) { return log('job', task.title, '由于运行次数超限而被暂停') } // 如果任务暂停或者已经完成 if ((task.suspended || task.checked) && !force) { return log('job', task.title, '由于账号未登录已暂停运行') } if (task && (task.frequency != 'never' || force)) { log('background', "run", task) incrementUsage(task) if (task.mode == 'iframe') { openByIframe(task.url, 'job') } else { chrome.tabs.create({ index: 1, url: task.url, active: false, pinned: true }, function (tab) { // 将标签页静音 chrome.tabs.update(tab.id, { muted: true }, function (result) { log('background', "muted tab", result) }) chrome.alarms.create('closeTab_'+tab.id, {delayInMinutes: 3}) }) } } } function openByIframe(src, type, delayTimes = 0) { // 加载新的任务 let iframeId = "iframe" let keepMinutes = 3 if (type == 'temporary') { iframeId = 'iframe' + Math.random().toString(36).substring(7); keepMinutes = 1 } // 当前任务过多则等待 if ($('iframe').length > 5 && delayTimes < 6) { setTimeout(() => { openByIframe(src, type, delayTimes + 1) }, (10 + rand(10)) * 1000); return console.log('too many iframe pages', src, delayTimes) } // 运行 resetIframe(iframeId) $("#" + iframeId).attr('src', src) // 设置重置任务 chrome.alarms.create(`${(type == 'temporary' ? 'destroyIframe' : 'clearIframe')}_${iframeId}`, { delayInMinutes: keepMinutes }) } function updateUnreadCount(change = 0) { let lastUnreadCount = localStorage.getItem('unreadCount') || 0 let unreadCount = parseInt(Number(lastUnreadCount) + change) if (unreadCount < 0) { unreadCount = 0 } localStorage.setItem('unreadCount', unreadCount); if (unreadCount > 0) { let unreadCountText = unreadCount.toString() if (unreadCount > 100) { unreadCountText = '99+' } chrome.browserAction.setBadgeText({ text: unreadCountText }); chrome.browserAction.setBadgeBackgroundColor({ color: "#4caf50" }); } else { chrome.browserAction.setBadgeText({ text: "" }); } } $( document ).ready(function() { log('background', "document ready") // 每20分钟运行一次定时任务 chrome.alarms.create('cycleTask', { periodInMinutes: 20 }) // 每600分钟完全重载 chrome.alarms.create('reload', {periodInMinutes: 600}) // 载入后马上运行一次任务查找 findJobs() // 载入显示未读数量 updateUnreadCount() // 加载任务参数 loadSettingsToLocalStorage('teaclub:task-parameters') loadSettingsToLocalStorage('teaclub:action-links') // 加载推荐设置 loadRecommendSettingsToLocalStorage() }) function openWebPageAsMobile(url) { chrome.windows.create({ width: 420, height: 800, url: url, type: "popup" }); } // 点击通知 chrome.notifications.onClicked.addListener(function (notificationId) { if (notificationId.split('_').length > 0) { let type = notificationId.split('_')[1] if (type && type.length > 1) { switch (type) { case 'fliggy': chrome.tabs.create({ url: "https://teaclub.zaoshu.so/sites/fliggy" }) break; default: chrome.tabs.create({ url: "https://teaclub.zaoshu.so/sites/taobao" }) } } } }) // 根据登录状态调整图标显示 function updateIcon() { let loginState = getLoginState() switch (loginState.class) { case 'alive': chrome.browserAction.getBadgeText({}, function (text){ if (text == "X" || text == " ! ") { chrome.browserAction.setBadgeText({ text: "" }); chrome.browserAction.setTitle({ title: "茶友会" }) } }) chrome.browserAction.setIcon({ path : { "19": "static/image/icon@19.png", "38": "static/image/icon@38.png" } }); chrome.contextMenus.removeAll(); break; case 'failed': chrome.browserAction.setBadgeBackgroundColor({ color: [190, 190, 190, 230] }); chrome.browserAction.setBadgeText({ text: "X" }); chrome.browserAction.setTitle({ title: "账号登录失效" }) chrome.contextMenus.removeAll(); chrome.contextMenus.create({ title: "账号登录失效,点击登录", contexts: ["browser_action"], onclick: function() { openLoginPage() } }); break; case 'warning': chrome.browserAction.setBadgeBackgroundColor({ color: "#EE7E1B" }); chrome.browserAction.setBadgeText({ text: " ! " }); chrome.contextMenus.removeAll(); chrome.contextMenus.create({ title: "账号登录失效,点击登录", contexts: ["browser_action"], onclick: function() { openLoginPage() } }); break; default: break; } } function openLoginPage() { chrome.tabs.create({ url: "https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm" }) } // 保存登录状态 function saveLoginState(loginState) { let previousState = getLoginState() localStorage.setItem('login-state_' + loginState.type, JSON.stringify({ time: new Date(), message: loginState.content || loginState.message, state: loginState.state })); chrome.runtime.sendMessage({ action: "loginState_updated", data: loginState }); // 如果登录状态从失败转换到了在线 if (previousState.class != 'alive' && loginState.state == "alive") { console.log('user account turn alive') setTimeout(() => { findJobs() }, 5000); setTimeout(() => { runJob() }, 15000); } } // 浏览器通知(合并) // mute_night function sendChromeNotification(id, content) { let hour = DateTime.local().hour; let muteNight = getSetting('mute_night'); if (muteNight && hour < 6) { log('background', 'mute_night', content); } else { chrome.notifications.create(id, content) log('message', id, content); } } function runTask(msg, sendResponse) { let task = getTask(msg.taskId) // set 临时运行 localStorage.setItem('temporary_job' + task.id + '_frequency', 'onetime'); // 任务因为频率受限无法运行 if (task.pause) { sendChromeNotification(new Date().getTime().toString(), { type: "basic", title: "任务因为频率受限无法运行", message: task.title + "已达到最大时段频率,每小时:" + task.rateLimit.hour, iconUrl: 'static/image/128.png' }) sendResponse({ result: "pause", message: "任务因为频率受限无法运行" }) } else { runJob(task.id, true) sendResponse({ result: "success" }) if (!msg.hideNotice) { sendChromeNotification(new Date().getTime().toString(), { type: "basic", title: "正在重新运行" + task.title, message: "任务运行大约需要2分钟,如果有情况我再叫你(请勿连续运行)", iconUrl: 'static/image/128.png' }) } } } function markCheckinStatus(msg) { let task = getTask(msg.taskId) if (task) { let year = new Date().getFullYear() let checkinKey = `checkin_${task.key}` let currentStatus = getSetting(checkinKey, null) let data = { date: DateTime.local().toFormat("o"), time: new Date(), value: msg.value } if (msg.month) { localStorage.setItem(`order-fliggy-${year}-${msg.month}`, 'Y'); } if (msg.orderId) { localStorage.setItem(`order-fliggy_${msg.orderId}`, 'Y'); } if (currentStatus && currentStatus.date == DateTime.local().toFormat("o")) { console.log('已经记录过今日签到状态了') } else { localStorage.setItem(checkinKey, JSON.stringify(data)); return data } } } function updateRunStatus(msg) { let task = getTask(msg.taskId) if (task) { localStorage.setItem('task-' + task.id + '_lasttime', new Date().getTime()) saveLoginState({ content: task.title + "成功运行", state: "alive", type: msg.mode || task.type[0] }) // 如果任务周期小于10小时,且不是计划任务,则安排下一次运行 if (mapFrequency[task.frequency] < 600 && !task.schedule) { chrome.alarms.create('runJob_' + task.id, { delayInMinutes: mapFrequency[task.frequency] }) } } } // 加载任务参数 function loadSettingsToLocalStorage(key) { $.getJSON(`https://teaclub.zaoshu.so/setting/${key}`, function (json) { saveSetting(key, json) }) } // 加载推荐设置 function loadRecommendSettingsToLocalStorage() { $.getJSON("https://teaclub.zaoshu.so/setting/teaclub:recommend", function (json) { if (json.displayPopup) { saveSetting('displayPopup', json.displayPopup) } if (json.events) { saveSetting('events', json.events) } if (json.announcements && json.announcements.length > 0) { saveSetting('announcements', json.announcements) } if (json.promotions) { saveSetting('promotions', json.promotions) } if (json.recommendedLinks && json.recommendedLinks.length > 0) { saveSetting('recommendedLinks', json.recommendedLinks) } else { localStorage.removeItem('recommendedLinks') } if (json.uninstallURL) { chrome.runtime.setUninstallURL(json.uninstallURL) } if (json.recommendServices && json.recommendServices.length > 0) { saveSetting('recommendServices', json.recommendServices) } }); } function sendMessageToPage(targetPage, data) { chrome.tabs.sendMessage(targetPage.tab.id, data, {}, function (response) { console.log('send message to tabs response', data, response) }) } function timeoutPromise(promise, ms) { return new Promise(function(resolve, reject) { setTimeout(function() { reject(new Error("timeout")) }, ms) promise.then(resolve, reject) }) } // 查找优惠券 async function searchCoupon(params) { try { let response = await timeoutPromise(fetch(`https://teaclub.zaoshu.so/coupon/search?sku=${params.sku}&keyword=${params.title}&merchant=${params.merchant}`), 10000) let details = await response.json(); return details; } catch (error) { console.error(error) return null } } // 处理消息通知 chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) { if (!msg.action) { msg.action = msg.text } let loginState = getLoginState() log('msg', new Date(), msg); switch(msg.action){ // 保存登录状态 case 'saveLoginState': saveLoginState(msg) break; // 获取登录状态 case 'getLoginState': sendResponse(loginState) break; // 打开登录页面 case 'openLogin': openLoginPage() break; // 保存变量值 case 'setVariable': localStorage.setItem(msg.key, JSON.stringify(msg.value)); break; // 获取设置 case 'getSetting': let setting = getSetting(msg.content) let temporarySetting = localStorage.getItem('temporary_' + msg.content) // 如果存在临时设置 if (temporarySetting) { // 临时设置5分钟失效 setTimeout(() => { localStorage.removeItem('temporary_' + msg.content) }, 60*5*1000); return sendResponse(temporarySetting) } sendResponse(setting) break; // 领取订单的里程 case 'getOrderFliggy': let orderStatus = localStorage.getItem(`order-fliggy_${msg.orderId}`) console.log('getOrderFliggy', msg.orderId, orderStatus) // 如果没有领取过 if (!(orderStatus && orderStatus == 'Y')) { let url = `https://www.fliggy.com/mytrip/?tvm=tcd&orderId=${msg.orderId}` setTimeout(() => { openByIframe(url, 'temporary') }, rand(20) * 1000); } sendResponse({ working: true }) break; case 'openUrlAsMobile': openWebPageAsMobile(msg.url) break; case 'option': localStorage.setItem(''+msg.title, msg.content); break; // 查询消息列表 case 'getMessages': setTimeout(async () => { await updateMessages() }, 50); break; // 手动运行任务 case 'runTask': runTask(msg, sendResponse) break; // 签到通知 case 'checkin_notice': let mute_checkin = getSetting('mute_checkin') if (mute_checkin && mute_checkin == 'checked' && !msg.test) { console.log('checkin', msg) } else { let icon = 'static/image/coin.png' let type = "basic" if (msg.type == 'mileage') { icon = 'static/image/mileage.png' type = 'fliggy' } sendChromeNotification( new Date().getTime().toString() + '_' + msg.reward, { type: type, title: msg.title, message: msg.content, iconUrl: icon }) } break; // 签到状态 case 'markCheckinStatus': let result = markCheckinStatus(msg) sendResponse({ result }) break; // 更新运行状态 case 'updateRunStatus': updateRunStatus(msg) sendResponse({ result: true }) break; // 查询优惠券 case 'queryCoupon': var disable_same_goods = getSetting('disable_same_goods') setTimeout(async () => { let result = await searchCoupon(msg.params) if (disable_same_goods) { result.similarGoods = null } sendMessageToPage(sender, { type: "couponInfo", content: result }) }, 50); sendResponse({ result: true }) break; case 'coupon': var coupon = msg.content var mute_coupon = getSetting('mute_coupon') if (mute_coupon && mute_coupon == 'checked') { console.log('coupon', msg) } else { sendChromeNotification( new Date().getTime().toString() + "_coupon_" + coupon.batch, { type: "basic", title: msg.title, message: coupon.name + coupon.price, isClickable: true, iconUrl: 'static/image/coupon.png' }) } break; case 'clearUnread': updateUnreadCount(-999) break; case 'myTab': sendResponse({ tab: sender.tab }); break; default: console.log("Received %o from %o, frame", msg, sender.tab, sender.frameId); } // 更新图标 updateIcon() // 保存消息 switch (msg.action) { case 'coupon': case 'notice': case 'checkin_notice': if (msg.test) { break; } let message = { type: msg.type || msg.action, // 通知的类型 batch: msg.batch, // 批次,通常是优惠券的属性 reward: msg.reward, // 奖励的类型 unit: msg.unit || msg.reward || msg.batch, // 奖励的单位 value: msg.value, // 奖励的数量 title: msg.title, content: msg.content, timestamp: Date.now() } let uuid = msg.uuid || Date.now() updateUnreadCount(1) setTimeout(async () => { await newMessage(uuid, message); }, 50); setTimeout(async () => { await updateMessages() }, 3000); break; } if (msg.text != 'saveAccount') { log('message', msg.text, msg); } // 如果消息 300ms 未被回复 return true }); Logline.keep(3); ================================================ FILE: src/components/app.vue ================================================ ================================================ FILE: src/components/discounts.vue ================================================ ================================================ FILE: src/components/events.vue ================================================ ================================================ FILE: src/components/guide.vue ================================================ ================================================ FILE: src/components/links.vue ================================================ ================================================ FILE: src/components/loading.vue ================================================ ================================================ FILE: src/components/popup.vue ================================================ ================================================ FILE: src/components/report.vue ================================================ ================================================ FILE: src/content_script.js ================================================ import 'weui'; import weui from 'weui.js'; import QRCode from "qrcode-svg"; import '../static/style/style.css' var observeDOM = (function () { var MutationObserver = window.MutationObserver || window.WebKitMutationObserver return function (obj, callback) { // define a new observer var obs = new MutationObserver(function (mutations, observer) { if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) { callback(observer); } }); // have the observer observe foo for changes in children obs.observe(obj, { childList: true, subtree: true }); }; })(); Object.defineProperty(Array.prototype, 'chunk', { value: function (chunkSize) { var array = this; return [].concat.apply([], array.map(function (elem, i) { return i % chunkSize ? [] : [array.slice(i, i + chunkSize)]; }) ); } }); function mockTap(element) { let rect = element.getBoundingClientRect() sendTouchEvent(rect.x + 3, rect.y + 3, element, 'touchstart'); sendTouchEvent(rect.x + 3, rect.y + 3, element, 'touchend'); } // 模拟点击 (原生) function simulateClick(domNode, mouseEvent) { if (mouseEvent && domNode) { return mockClick(domNode) } try { mockTap(domNode) mockClick(domNode) } catch (error) { console.log('fullback to mockClick', error) mockClick(domNode) } } function mockClick(element) { var dispatchMouseEvent = function (target, var_args) { var e = document.createEvent("MouseEvents"); e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1)); target.dispatchEvent(e); }; if (element) { dispatchMouseEvent(element, 'mouseover', true, true); dispatchMouseEvent(element, 'mousedown', true, true); dispatchMouseEvent(element, 'click', true, true); dispatchMouseEvent(element, 'mouseup', true, true); } } /* eventType is 'touchstart', 'touchmove', 'touchend'... */ function sendTouchEvent(x, y, element, eventType) { if ('TouchEvent' in window && TouchEvent.length > 0) { const touchObj = new Touch({ identifier: Date.now(), target: element, clientX: x, clientY: y, radiusX: 2.5, radiusY: 2.5, rotationAngle: 10, force: 0.5, }); const touchEvent = new TouchEvent(eventType, { cancelable: true, bubbles: true, touches: [touchObj], targetTouches: [], changedTouches: [touchObj], shiftKey: true, }); element.dispatchEvent(touchEvent); } else { console.log('no TouchEvent') } } function injectScript(file, node) { var th = document.getElementsByTagName(node)[0]; var s = document.createElement('script'); s.setAttribute('type', 'text/javascript'); s.setAttribute('charset', "UTF-8"); s.setAttribute('src', file); th.appendChild(s); } function injectScriptCode(code, node = 'body') { var th = document.getElementsByTagName(node)[0]; var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.setAttribute('language', 'JavaScript'); script.textContent = code; th.appendChild(script); } injectScriptCode(` if (typeof hrl != 'undefined' && typeof host != 'undefined') { document.write(''); document.getElementById('exe').click() } `, 'body') function escapeSpecialChars(jsonString) { 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"); } var pageTaskRunning = false // 获取设置 function getSetting(name, cb) { chrome.runtime.sendMessage({ text: "getSetting", content: name }, function (response) { cb(response) console.log("getSetting Response: ", name, response); }); } function createElementFromHTML(htmlString) { var div = document.createElement('div'); div.innerHTML = htmlString.trim(); return div.firstChild; } function addDiscountElement() { var newDiv = createElementFromHTML(`
茶友会正在查询优惠券..
🍵茶友会提供
`); if (document.getElementById("J_isku")) { document.getElementsByClassName("tb-wrap")[0].insertBefore(newDiv, document.getElementById("J_SepLine")); } else { document.getElementsByClassName("tb-wrap")[0].insertBefore(newDiv, document.getElementsByClassName("tm-ser")[0]); } } function addCouponElement(coupon) { let displayCouponName = coupon.name const couponNameParsingResults = /满([0-9]*)(.[0-9]{2})?元减([0-9]*)(.[0-9]{2})?元/.exec(coupon.name) if (couponNameParsingResults && couponNameParsingResults[2] == "00") { displayCouponName = `满${couponNameParsingResults[1]}元减${couponNameParsingResults[3]}元` } const couponQrcode = new QRCode({ width: 70, height: 70, background: "#de1d3d", content: coupon.shortUrl }).svg(); var newDiv = createElementFromHTML(`
${couponQrcode}

${displayCouponName}

剩余:${coupon.remainCount}

有效期

${coupon.startTime}

- ${coupon.endTime}

`); var currentDiv = document.getElementById("Coupon-list"); currentDiv.appendChild(newDiv); } function buildGoodsBatch(goodsBatch) { injectScriptCode( ` var slideIndex = 1; // Next/previous controls function plusSlides(n) { showSlides(slideIndex += n); } // Thumbnail image controls function currentSlide(n) { showSlides(slideIndex = n); } function showSlides(n) { var i; var slides = document.getElementsByClassName("teaClubSlide"); var dots = document.getElementsByClassName("dot"); if (n > slides.length) {slideIndex = 1} if (n < 1) {slideIndex = slides.length} for (i = 0; i < slides.length; i++) { slides[i].style.display = "none"; } for (i = 0; i < dots.length; i++) { dots[i].className = dots[i].className.replace(" active", ""); } if (slides[slideIndex-1]) { slides[slideIndex-1].style.display = "block"; dots[slideIndex-1].className += " active"; } } `, 'body') const goodsBatchDom = goodsBatch.map((goods, index) => { return `
${index} / ${goodsBatch.length}
${ goods.map((good) => { return buildGoodCard(good) }).join('') }
` }).join('') const batchDots = goodsBatch.map((goods, index) => { return `` }).join('') let goodsElement = '' if (goodsBatch.length > 1) { goodsElement = createElementFromHTML(`
${goodsBatchDom}

${batchDots}
`) } else { goodsElement = createElementFromHTML(goodsBatchDom) } var currentDiv = document.getElementById("PDD-goods"); currentDiv.appendChild(goodsElement); } function buildGoodCard(good) { const goodQrcode = new QRCode({ width: 100, height: 100, content: good.url }).svg(); return `
${goodQrcode}
${good.name}
销量:${good.sales}
¥${good.price}
去购买
` } async function findCoupon(disable_find_coupon) { if (disable_find_coupon) return addDiscountElement() const urlParams = new URLSearchParams(window.location.search); const sku = urlParams.get('id') || urlParams.get('skuId') const title = document.title.split('-')[0] const merchant = window.location.host.indexOf('item.taobao.com') > -1 ? 'taobao' : 'tmall' chrome.runtime.sendMessage({ action: "queryCoupon", params: { merchant, sku, title } }) } function markCheckinStatus(task, data, cb) { chrome.runtime.sendMessage({ action: "markCheckinStatus", taskId: task.id, status: "signed", ...data }, function (response) { console.log('markCheckinStatus response', response) if (cb && response) { cb() } }); } // ********* // 签到任务 // ********* // 飞猪里程 function markFliggyCheckin(task, orderId) { const signRes = document.getElementsByClassName("tlc-title")[0] && document.getElementsByClassName("tlc-title")[0].innerText 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 console.log('markFliggyCheckin', task, orderId, signRes, value) if (signRes && (signRes.indexOf("获得") > -1)) { return markCheckinStatus(task, { value: value + '里程', orderId: orderId }, () => { chrome.runtime.sendMessage({ action: "checkin_notice", value: value, reward: 'mileage', title: orderId ? "茶友会自动为您签订单奖励里程" : "茶友会自动为您签到领里程", content: "恭喜您获得了" + value + '个里程奖励' }, function (response) { console.log("Response: ", response); }) }) } else if (signRes && (signRes.indexOf("今日已领") > -1)) { markCheckinStatus(task, { value }) } else if (signRes && (signRes.indexOf("已领取过") > -1) && orderId) { markCheckinStatus(task, { value, orderId: orderId }) } else if (signRes && (signRes.indexOf("本月您已领满") > -1)) { markCheckinStatus(task, { value, month: new Date().getMonth(), }) } } function fliggyCheckin(setting) { if (setting != 'never') { weui.toast('茶友会运行中', 1000); chrome.runtime.sendMessage({ action: "updateRunStatus", taskId: 2 }) let signInButton = document.getElementsByClassName("J_mySignInBtn")[0] if (signInButton && signInButton.innerText == "已签到") { markCheckinStatus({ key: 'fliggy-mytrip', id: 2 }) } else if (signInButton && signInButton.innerText && signInButton.innerText.indexOf("签到") > -1) { simulateClick(signInButton) // 监控结果 observeDOM(document.body, function () { markFliggyCheckin({ key: 'fliggy-mytrip', id: 2 }) }) } } } function fliggyCheckin2(setting) { if (setting != 'never') { weui.toast('茶友会运行中', 1000); chrome.runtime.sendMessage({ action: "updateRunStatus", taskId: 3 }) let signInButton = document.getElementsByClassName("J_makesurebuttontvipBtn")[0] if (signInButton && signInButton.innerText && signInButton.innerText == "确    认") { simulateClick(signInButton) // 监控结果 observeDOM(document.body, function () { markFliggyCheckin({ key: 'fliggy-tvip', id: 3 }) }) } else { if (signInButton && signInButton.innerText == "已签到") { markCheckinStatus({ key: 'fliggy-tvip', id: 3 }) } } } } function fliggyCheckin3(setting) { if (setting != 'never') { weui.toast('茶友会运行中', 1000); chrome.runtime.sendMessage({ action: "updateRunStatus", taskId: 4 }) let signInButton = null let signInReward = null let spanElements = document.getElementsByTagName("span") Array.prototype.slice.call(spanElements).forEach(function (element) { if (element.innerText && /^签到\+[0-9]+里程/.test(element.innerText)) { signInButton = element } if (element.innerText && /^明日\+[0-9]+里程/.test(element.innerText)) { signInReward = element } }); console.log('signInButton', signInButton) if (signInButton) { setTimeout(() => { simulateClick(signInButton, true) }, 500); // 监控结果 observeDOM(document.body, function () { markFliggyCheckin({ key: 'rx-member', id: 4 }) }) } else { if (signInReward && signInReward.innerText) { markCheckinStatus({ key: 'rx-member', id: 4 }) } } } } function fliggyCheckin6(setting) { if (setting != 'never') { weui.toast('茶友会运行中', 1000); chrome.runtime.sendMessage({ action: "updateRunStatus", taskId: 6 }) const urlParams = new URLSearchParams(window.location.search); let orderId = urlParams.get('orderId') let signInButton = document.getElementsByClassName("J_makesurebuttontvip")[0] if (signInButton && signInButton.innerText && signInButton.innerText == "确    认") { simulateClick(signInButton) // 监控结果 observeDOM(document.body, function () { markFliggyCheckin({ key: 'order-fliggy', id: 6 }, orderId) }) } } } function fliggyCheckin7(setting) { if (setting != 'never') { weui.toast('茶友会运行中', 1000); chrome.runtime.sendMessage({ action: "updateRunStatus", taskId: 7 }) let signInButton = document.getElementsByClassName("check-btn")[0] if (signInButton && signInButton.innerText && signInButton.innerText == "立即签到") { simulateClick(signInButton) // 监控结果 observeDOM(document.body, function () { markFliggyCheckin({ key: 'welfare-center', id: 7 }) }) } else { if (signInButton && signInButton.innerText == "上飞猪App领更多") { markCheckinStatus({ key: 'welfare-center', id: 7 }) } } } } function accountAlive(type, message) { chrome.runtime.sendMessage({ action: "saveLoginState", state: "alive", message: message, type: type }, function (response) { console.log("accountAlive ", type, message, response); }); } if (document.getElementById("login-info")) { observeDOM(document.getElementById("login-info"), function () { if (document.getElementsByClassName("j_Username")[0] && document.getElementsByClassName("j_Username")[0].innerText) { accountAlive('pc', 'PC网页检测到用户名') } }); } // 主任务 function CheckDom() { if (window.location.host.indexOf("m.taobao.com") > -1 && window.location.host.indexOf("item.taobao.com") < 0) { if (window.location.host != "market.m.taobao.com") { injectScript(chrome.extension.getURL('/static/touch-emulator.js'), 'body'); injectScriptCode(` setTimeout(function () { TouchEmulator(); }, 200) `, 'body') } } // 判断登录状态 setTimeout(() => { checkLoginState() }, 1000) setTimeout(() => { if (window.location.host == 'login.taobao.com') { chrome.runtime.sendMessage({ action: "saveLoginState", state: "failed", message: "PC网页需要登录", type: "pc" }, function (response) { console.log("Response: ", response); }); } if (window.location.host == 'login.m.taobao.com') { chrome.runtime.sendMessage({ action: "saveLoginState", state: "failed", message: "移动网页需要登录", type: "m" }, function (response) { console.log("Response: ", response); }); } }, 8000); // 订单 if (document.title == "已买到的宝贝" && window.location.host == 'buyertrade.taobao.com') { let orderElements = document.getElementsByClassName("bought-wrapper-mod__head-info-cell___29cDO") let time = 0 // 只处理最近五个订单 if (orderElements && orderElements.length > 5) { orderElements = Array.prototype.slice.call(orderElements).slice(0, 5); } if (orderElements) { accountAlive('pc', 'PC网页检测订单') } Array.prototype.slice.call(orderElements).forEach(function (orderElement) { if (orderElement.lastElementChild && orderElement.lastElementChild.lastElementChild) { let orderId = orderElement.lastElementChild.lastElementChild.innerText if (orderId) { setTimeout(function () { chrome.runtime.sendMessage({ action: "getOrderFliggy", orderId: orderId }, function (response) { console.log("Response: ", response); }); }, time) time += 15000; } } }); } // 商品页 if (window.location.host.indexOf('item.taobao.com') > -1 || window.location.host.indexOf('detail.tmall.com') > -1) { setTimeout(() => { getSetting('disable_find_coupon', (setting) => { findCoupon(setting) }) }, 50); } // 飞猪签到 if (document.title == "我的旅行" && window.location.host == 'www.fliggy.com') { setTimeout(() => { if (document.getElementsByClassName("J_mySignInBtn")[0]) { getSetting('task-2_frequency', fliggyCheckin) } else if (document.getElementsByClassName("J_makesurebuttontvipBtn")[0]) { getSetting('task-3_frequency', fliggyCheckin2) } else if (document.getElementsByClassName("J_makesurebuttontvip")[0]) { getSetting('task-6_frequency', fliggyCheckin6) } }, 3000); }; if (document.title == "会员中心" && window.location.host == 'h5.m.taobao.com') { getSetting('task-4_frequency', fliggyCheckin3) } if (document.title == "里程福利中心" && window.location.host == 'h5.m.taobao.com') { getSetting('task-7_frequency', fliggyCheckin7) } } // 检查登录状态 function checkLoginState() { // PC 是否登录 if (document.getElementById("mtb-nickname") && document.getElementById("mtb-nickname").value || document.getElementsByClassName("J_MemberNick")[0]) { accountAlive('pc', 'PC网页检测到用户名') } if (document.getElementById("J_SiteNavLogin")) { if (document.getElementById("J_SiteNavLogin").querySelector(".site-nav-login-info-nick") && document.getElementById("J_SiteNavLogin").querySelector(".site-nav-login-info-nick").text) { accountAlive('pc', 'PC网页检测到用户头像') } } // M 是否登录 if (document.getElementsByClassName("tb-toolbar-container")[0] || window.location.href == "https://h5.m.taobao.com/mlapp/mytaobao.html") { accountAlive('m', '移动端打开我的淘宝') } if (window.location.href == "https://main.m.taobao.com/mytaobao/index.html") { if (document.getElementsByClassName(".main-layout")[0].querySelector(".tpl-wrapper")) { accountAlive('m', '移动端打开我的淘宝') } } } $(document).ready(function () { console.log('茶友会注入页面成功'); checkLoginState() if (!pageTaskRunning) { setTimeout(function () { console.log('茶友会开始执行任务'); CheckDom() }, 2500) } }); function dealWithSearchRes(content) { if (content.coupon) { setTimeout(() => { document.getElementById("Coupon-box").style.display = 'block'; addCouponElement(content.coupon) document.getElementById("teaclub").getElementsByClassName("loading")[0].style.display = 'none'; }, 500); } else { document.getElementById("Coupon-box").style.display = 'none'; } if (content.specialEvent && content.specialEvent.html) { document.getElementById("specialEvent-box").style.display = 'block'; const specialEventElement = createElementFromHTML(content.specialEvent.html) const containerDiv = document.getElementById("specialEvent-container"); containerDiv.appendChild(specialEventElement); } if (content.similarGoods && content.similarGoods.length > 0) { setTimeout(() => { document.getElementById("teaclub").getElementsByClassName("loading")[0].style.display = 'none'; document.getElementById("PDD-box").style.display = 'block'; buildGoodsBatch(content.similarGoods.chunk(3)) }, 500); setTimeout(() => { injectScriptCode(` showSlides(1); `, 'body') }, 520); } else { document.getElementById("PDD-box").style.display = 'none'; } if (!content.coupon && (!content.similarGoods || content.similarGoods.length < 1)) { setTimeout(() => { document.getElementById("teaclub").getElementsByClassName("loading")[0].style.display = 'none'; document.getElementById("No-Result").style.display = 'block'; }, 1500); } } // 应用消息 chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { console.log('onMessage', message) switch (message.type) { case 'couponInfo': dealWithSearchRes(message.content) break; default: break; } }) // 消息 var passiveSupported = false; try { var options = Object.defineProperty({}, "passive", { get: function () { passiveSupported = true; } }); window.addEventListener("test", null, options); } catch (err) { } window.addEventListener("message", function (event) { if (event.data && event.data.action == 'productPrice') { findOrderBySkuAndApply(event.data, event.data.setting) } }, passiveSupported ? { passive: true } : false ); var nodeList = document.querySelectorAll('script'); for (var i = 0; i < nodeList.length; ++i) { var node = nodeList[i]; node.src = node.src.replace("http://", "https://") } ================================================ FILE: src/popup.js ================================================ import * as _ from "lodash" $ = window.$ = window.jQuery = require('jquery') import 'weui' import weui from 'weui.js' import Vue from 'vue' import microtip from 'microtip/microtip.css' import '../static/style/popup.css' $.each(['show', 'hide'], function (i, ev) { var el = $.fn[ev]; $.fn[ev] = function () { this.trigger(ev); return el.apply(this, arguments); }; }); import App from './components/app.vue'; import VueLazyload from 'vue-lazyload' Vue.use(VueLazyload) new Vue({ el: '#app', render: h => h(App) }) // 消息已读 function readMessage() { chrome.runtime.sendMessage({ text: "clearUnread" }, function (response) { console.log("Response: ", response); }); } $( document ).ready(function() { // 标记已读 readMessage() // 查询最新版本 $.getJSON(`https://teaclub.zaoshu.so/updates?buildid=${process.env.BUILDID}&browser=${process.env.BROWSER}&app=teaclub`, function (lastVersion) { if (!lastVersion) return localStorage.removeItem('newVersion') let skipBuildId = localStorage.getItem('skipBuildId') let localBuildId = skipBuildId || process.env.BUILDID // 如果有新版 if (localBuildId < lastVersion.buildId) { localStorage.setItem('newVersion', lastVersion.versionCode) // 如果新版是主要版本,而且当前版本需要被提示 if (lastVersion.major && localBuildId < lastVersion.noticeBuildId) { let noticeDialog = weui.dialog({ title: `${lastVersion.title} ×` || '有版本更新', content: `${lastVersion.changelog}
${lastVersion.time}` + (lastVersion.blogUrl ? `了解更多` : '') + `
`, className: 'update', buttons: [{ label: '不再提醒', type: 'default', onClick: function () { localStorage.setItem('skipBuildId', lastVersion.buildId) } }, { label: '下载更新', type: 'primary', onClick: function () { chrome.tabs.create({ url: lastVersion.downloadUrl || `https://teaclub.zaoshu.so/updates/latest?browser=${process.env.BROWSER}&app=teaclub` }) } }] }); $(".update .dismiss").on("click", function () { noticeDialog.hide() }) } } else { localStorage.removeItem('newVersion') } }); $('.settings .weui-navbar__item').on('click', function () { $(this).addClass('weui-bar__item_on').siblings('.weui-bar__item_on').removeClass('weui-bar__item_on'); var type = $(this).data('type') $('.settings_box').hide() $('.settings_box.' + type).show() }); $(document).on("click", ".openMobilePage", function () { chrome.runtime.sendMessage({ action: "openUrlAsMobile", url: $(this).data('url') }, function (response) { console.log("Response: ", response); }); }) $(".weui-dialog__ft a").on("click", function () { $("#dialogs").hide() $("#listenAudio").hide() $("#changeLogs").hide() }) $("#dialogs .js-close").on("click", function () { $("#dialogs").hide() }) }) // 防止缩放 chrome.tabs.getZoomSettings(function (zoomSettings) { if (zoomSettings.defaultZoomFactor > 1 && zoomSettings.scope == 'per-origin' && zoomSettings.mode == 'automatic') { let zoomPercent = (100 / (zoomSettings.defaultZoomFactor * 100)) * 100; document.body.style.zoom = zoomPercent + '%' } }) ================================================ FILE: src/start.js ================================================ import 'weui' import '../static/style/start.css' ================================================ FILE: src/tasks.js ================================================ import {DateTime} from 'luxon' import { getLoginState } from './account' import { getSetting, readableTime } from './utils' const frequencyOptionText = { '2h': "每2小时", '5h': "每5小时", 'daily': "每天", 'never': "从不" } const mapFrequency = { '2h': 2 * 60, '5h': 5 * 60, 'daily': 24 * 60, 'never': 99999 } const tasks = [ { id: '6', src: { pc: 'https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm', }, title: '订单里程', description: "每个淘宝订单可以领取3个飞猪里程(每月可领5次)", mode: 'iframe', key: "order-fliggy", type: ['pc'], checkin: true, frequencyOption: ['daily', 'never'], frequency: 'daily', rateLimit:{ daily: 5, hour: 2 } }, { id: '2', src: { pc: 'https://www.fliggy.com/mytrip/', }, baseUrl: "https://www.fliggy.com/mytrip/", title: '飞猪里程1', description: "每日签到领取飞猪里程", mode: 'iframe', key: "fliggy-mytrip", type: ['pc'], checkin: true, frequencyOption: ['daily', 'never'], frequency: 'daily', rateLimit:{ daily: 5, hour: 2 } }, { id: '3', src: { pc: 'https://www.fliggy.com/mytrip/?tvm=tvip', }, title: '飞猪里程2', description: "每日签到领取飞猪里程", mode: 'iframe', key: "fliggy-tvip", type: ['pc'], checkin: true, frequencyOption: ['daily', 'never'], frequency: 'daily', rateLimit:{ daily: 5, hour: 2 } }, { id: '4', src: { m: 'https://h5.m.taobao.com/trip/rx-member/index/index.html?_projVer=0.1.25', }, title: '飞猪里程3', description: "飞猪移动页每日签到里程", mode: 'iframe', key: "rx-member", type: ['m'], checkin: true, frequencyOption: ['daily', 'never'], frequency: 'daily', rateLimit:{ daily: 5, hour: 2 } }, { id: '7', src: { m: 'https://h5.m.taobao.com/trip/welfare-center/mileage/index.html', }, title: '飞猪里程5', description: "飞猪签到领里程", mode: 'iframe', key: "welfare-center", type: ['m'], checkin: true, frequencyOption: ['daily', 'never'], frequency: 'daily', rateLimit:{ daily: 5, hour: 2 } } ] // 根据登录状态选择任务模式 let findTaskPlatform = function (task) { let loginState = getLoginState() let platform = null if (loginState.class == 'alive') { platform = task.type[0]; } return platform } let getTask = function (taskId, currentPlatform) { let taskParameters = getSetting('teaclub:task-parameters', []) let parameters = (Array.isArray(taskParameters) && taskParameters.length > 0) ? taskParameters.find(t => t.id == taskId.toString()) : {} let task = Object.assign({}, tasks.find(t => t.id == taskId.toString()), parameters) let taskStatus = {} let year = new Date().getFullYear() let today = DateTime.local().toFormat("o") let hour = new Date().getHours() taskStatus.usage = { hour: getSetting(`temporary:usage-${taskId}_${year}d:${today}:h:${hour}`, 0), daily: getSetting(`temporary:usage-${taskId}_${year}d:${today}`, 0) } taskStatus.platform = findTaskPlatform(task); taskStatus.frequency = getSetting(`task-${taskId}_frequency`, task.frequency) taskStatus.last_run_at = localStorage.getItem(`task-${task.id}_lasttime`) ? parseInt(localStorage.getItem(`task-${task.id}_lasttime`)) : null taskStatus.last_run_description = taskStatus.last_run_at ? "上次运行: " + readableTime(DateTime.fromMillis(Number(taskStatus.last_run_at))) : "从未执行"; // 如果是签到任务,则读取签到状态 if (task.checkin) { let checkinRecord = getSetting(`checkin_${task.key}`, null) if (checkinRecord && checkinRecord.date == DateTime.local().toFormat("o")) { taskStatus.checked = true taskStatus.checkin_description = "完成于:" + readableTime(DateTime.fromISO(checkinRecord.time)) + (checkinRecord.value ? ",领到:" + checkinRecord.value : ""); } } // 订单里程任务每月5次 if (task.id == "6") { let year = new Date().getFullYear() let month = new Date().getMonth() let monthStatus = localStorage.getItem(`order-fliggy-${year}-${month}`) if (monthStatus && monthStatus == 'Y') { taskStatus.checked = true taskStatus.checkin_description = "本月已领取五次" } } // 如果限定平台 if (currentPlatform) { if (task.type && task.type.indexOf(currentPlatform) < 0) { taskStatus.unavailable = true } } // 选择运行平台 if (!task.url) { taskStatus.url = taskStatus.platform ? task.src[taskStatus.platform] : task.src[task.type[0]]; } // 如果任务无可运行平台 if (!taskStatus.platform) { taskStatus.suspended = true; taskStatus.platform = task.type[0]; } // 如果超出限制 if (taskStatus.usage.daily >= task.rateLimit.daily || taskStatus.usage.hour >= task.rateLimit.hour) { taskStatus.pause = true; } return Object.assign(task, taskStatus) } let getTasks = function (currentPlatform) { let taskList = tasks.map((task) => { return getTask(task.id, currentPlatform) }) return taskList.filter(task => !(task.unavailable || task.deprecated)); } export { frequencyOptionText, mapFrequency, tasks, getTask, getTasks, findTaskPlatform }; ================================================ FILE: src/utils.js ================================================ import { DateTime } from 'luxon' export const rand = function (n) { return (Math.floor(Math.random() * n + 1)); } export const price = function (price) { return Number(Number(price).toFixed(2)) } export const getSetting = function (settingKey, defaultValue) { let setting = localStorage.getItem(settingKey) if (setting) { try { setting = JSON.parse(setting) } catch (error) { } } return setting ? setting : defaultValue } export const saveSetting = function (settingKey, value) { return localStorage.setItem(settingKey, JSON.stringify(value)) } export const readableTime = function (dateTime) { if (DateTime.local().hasSame(dateTime, 'day')) { return '今天 ' + dateTime.setLocale('zh-cn').toLocaleString(DateTime.TIME_SIMPLE) } if (DateTime.local().hasSame(dateTime.plus({ days: 1 }), 'day')) { return '昨天 ' + dateTime.setLocale('zh-cn').toLocaleString(DateTime.TIME_SIMPLE) } return dateTime.setLocale('zh-cn').toFormat('f') } export const versionCompare = function (v1, v2, options) { var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, v1parts = v1.split('.'), v2parts = v2.split('.'); function isValidPart(x) { return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); } if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { return NaN; } if (zeroExtend) { while (v1parts.length < v2parts.length) v1parts.push("0"); while (v2parts.length < v1parts.length) v2parts.push("0"); } if (!lexicographical) { v1parts = v1parts.map(Number); v2parts = v2parts.map(Number); } for (var i = 0; i < v1parts.length; ++i) { if (v2parts.length == i) { return 1; } if (v1parts[i] == v2parts[i]) { continue; } else if (v1parts[i] > v2parts[i]) { return 1; } else { return -1; } } if (v1parts.length != v2parts.length) { return -1; } return 0; } ================================================ FILE: src/variables.js ================================================ module.exports = { stateText: { "failed": "失败", "alive": "有效", "unknown": "未知" }, recommendServices: [ { link: "https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=8c3eff7793dd70781315d9b5c9727c39&from=console", title: "腾讯云新客礼包", description: "新客户无门槛领取2775元代金券", class: "el-tag el-tag--success" }, { link: "https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=sqj7d3bm", title: "阿里云优惠券", description: "领取阿里云全品类优惠券", class: "el-tag" }, ] }; ================================================ FILE: static/style/popup.css ================================================ @media (prefers-color-scheme:dark) { body:not([data-weui-theme='light']) { background-color: #1a1919; color: #999; } body:not([data-weui-theme='light']) .messages, body:not([data-weui-theme='light']) .discounts { background-color: transparent; } body:not([data-weui-theme='light']) .weui-navbar__item.weui-bar__item_on.zaoshu-tab { color: #b9b9b9; background: #661c1a91; } } @media (prefers-color-scheme:light) { body:not([data-weui-theme='dark']) { background-color: #fff; color: #333; transition: background-color 0.3s ease; } } select { text-indent: 0.01px; text-overflow: ''; -moz-appearance: none; } a { color: #0b85c3; } a:hover { color: #006da5; } html, body { min-height: 580px; min-width: 780px; overflow-x: hidden; overflow-y: hidden; } .popup{ height: 600px; width: 800px; overflow: hidden; } .weui-cell_select { height: 34px; font-size: 15px; padding: 2px 15px; } .frequency_settings .weui-cell__bd i.show { font-size: 20px; margin-left: -5px; display: inline-block !important; height: 20px; margin-top: -2px; } .page__hd { padding: 10px; } .weui-dialog { max-width: 440px; } .page__desc { padding: 5px 10px; } .settings { width: 45%; background: #cccccc1f; border-right: 1px solid var(--weui-FG-3); } .settings .weui-cell_switch { height: 32px; font-size: 15px; padding-top: 4px; padding-bottom: 4px; } .contents { width: 55%; overflow: hidden; height: 600px; } .weui-navbar__item.weui-bar__item_on.zaoshu-tab { color: #921714; } .weui-navbar__item.weui-bar__item_on{ color: var(--weui-BG-4); font-weight: bold; } .contents .weui-tab { height: 550px; } .weui-cell_switch { height: 32px; font-size: 16px; } .contents .weui-tab .weui-badge { margin-left: 5px; margin-top: -3px; background-color: #4CAF50; } .contents .weui-tab .weui-badge.new-discounts { background-color: #b9201d; padding: 0.3em; position: absolute; } .other_actions { padding: 10px; padding-bottom: 0; } .other_actions p { padding: 5px 0; } .no_order, .no_message { background: url(../image/empty.svg) no-repeat center 10px; padding: 5em 0em; text-align: center; margin-top: 10em; opacity: 0.5; padding-top: 7em; } .no_message .tips{ font-size: 12px; margin-top: .5em; } .bottom-tips { padding: 5px; } .other_actions h3 { font-size: 16px; } .recommendation { height: 210px; font-size: 12px; } .tips .weui-btn_mini { padding: 0.1em .5em; line-height: 1.4; margin-bottom: -7px; font-size: 12px; } .reward_tips .newyear { color: #f15f5f; } #renderFrame { height: 0px; } .reload-icon { cursor: pointer; } .frequency_settings .weui-cell__bd { line-height: 34px; } .frequency_settings .weui-icon-waiting-circle { font-size: 19px; } .switch-paymethod { cursor: pointer; } #notice { color: #333; } .alipay_action { line-height: 26px; height: 24px; width: 120px; margin: 0 auto; } .alipay_action svg { float: left; } .reload-icon { background: url(../image/reload.svg) no-repeat 1px 1px; width: 20px; height: 21px; color: #dcdcdc; display: inline-block; vertical-align: middle; background-size: 17px; } .orders li, .messages li { display: block; } .orders .order_time { position: relative; } .orders .show-order { -webkit-mask: url(../image/show.svg) no-repeat center; mask: url(../image/show.svg) no-repeat center; -webkit-mask-size: 16px; mask-size: 16px; } .orders .show-order, .orders .hide-order { width: 20px; height: 21px; color: #dcdcdc; display: inline-block; vertical-align: middle; background-size: 16px; cursor: pointer; background-color: #ccc; position: absolute; right: 5px; top: 1px; } .orders .hide-order { -webkit-mask: url(../image/hide.svg) no-repeat center; mask: url(../image/hide.svg) no-repeat center; -webkit-mask-size: 16px; mask-size: 16px; } .logo { display: inline-block; } .order_time { margin-top: .77em; margin-bottom: .3em; padding-left: 15px; padding-right: 15px; color: #999; font-size: 12px; padding-top: 5px; } #orders .weui-cell:before, .contents-box.weui-cells:before, .contents-box.weui-cells:after { border-top: none; content: none; } .orders .good_title { height: 55px; } .good_title { font-size: 12px; height: 80px; display: block; clear: both; width: 98%; } .orders .good_title p { margin-left: 65px; } .self-recommendation p.tips { font-size: 12px; text-align: center; padding: 1em; color: #ccc; } .good_title p { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; max-height: 78px; -webkit-line-clamp: 3; -moz-box-orient: vertical; -webkit-box-orient: vertical; padding-bottom: 5px; line-height: 18px; margin-left: 85px; } .orders .good_title img { width: 55px; height: 55px; } .good_title img { display: inline-block; position: absolute; left: 15px; top: 10px; padding-right: 10px; width: 75px; height: 75px; overflow: hidden; } .good_title .description { font-size: 12px; color: #666; max-height: 35px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -moz-box-orient: vertical; -webkit-box-orient: vertical; } .good_title span.count { color: #949494; } img.promotion_title { display: inline-block; padding-right: 10px; width: 55px; height: 55px; overflow: hidden; float: left; position: unset; } .good_title a { font-size: 13px; } .promotion_price { font-size: 14px; color: #1aad19; display: block; font-weight: 500; padding-bottom: 5px; } .changelog .time { font-size: 12px; color: #666; } .changelog .blog { float: right; color: #888; font-size: 12px; text-decoration: none; line-height: 24px; } .go-buy { background: #1aad19; color: #fff; font-size: 14px; padding: .3em .5em; border-radius: 2px; } .orders .dismiss { float: right; padding: 2px; position: absolute; top: -2px; right: 6px; font-size: 14px; cursor: pointer; } .weui-cell.promotion { background: #feedba54; } .buy-btn { padding: 2px 5px; line-height: 1.3; font-size: 12px; margin-top: 4px; } a.buy-btn:hover { color: #efefef; } .good { background: #fdfdfd; border-bottom: 1px solid #f3f3f3; border-top: 1px solid #f3f3f3; } .good+.good { border-top: none; } .success_log { font-size: 12px; margin: 10px; color: #690; } .order_price { font-size: 12px; color: #666; display: block; } #dialogs, #changeLogs{ display: none; } .zaoshu-icon { width: auto; height: 15px; margin-top: -4px; vertical-align: middle; } .guide .weui-dialog__bd { line-height: 1.6; max-height: 300px; overflow-x: hidden; } .guide .weui-dialog__bd p { margin-bottom: 1em; } .testbox p { text-align: left; } .new_price { font-size: 14px; color: #333; } .new_price.up { color: #690; } .new_price.down { color: #ea2222; } .time { text-align: right; } .alipay_pay { display: none; } .alipay_pay img { padding: 24px; background: #fff; } .weui-dialog__ft a { cursor: pointer; } .weui-dialog .segmented-control { width: 80%; margin: 0 auto; border: 1px solid #eee; border-radius: 4px; } .segmented-control { display: table; width: 100%; margin: 2em 0; padding: 0; background: #fff; } .segmented-control__item:first-child { float: left; } .segmented-control__item:last-child { float: right; } .segmented-control__item:first-child .segmented-control__label { border-radius: 4px 0 0 4px; } .segmented-control__item:last-child .segmented-control__label { border-radius: 0 4px 4px 0; } .segmented-control__item { width: 49.5%; display: inline-block; margin: 0; padding: 0; list-style-type: none; } .segmented-control__input { position: absolute; visibility: hidden; } .segmented-control__label { display: block; margin: 0 -1px -1px 0; /* -1px margin removes double-thickness borders between items */ padding: .45em .25em; font: 14px/1.5 sans-serif; text-align: center; cursor: pointer; } .segmented-control__label:hover { background: #fafafa; } .checked .segmented-control__label { background: #1aad19; color: #fff; } .auto_login { font-size: 14px; margin: 10px 5px; } .weui-navbar__item { padding: 7px 0; cursor: pointer; font-size: 15px; color: var(--weui-TAG-TEXT-BLACK); } .recommendedLink p { text-align: center; margin-top: 5px; } .iframe-loading { z-index: 0 !important; } .js-close { position: absolute; right: 10px; top: 1px; padding: 4px; font-size: 18px; cursor: pointer; } .settings_box { overflow-y: auto; overflow-x: hidden; height: 515px; } .settings .page__desc { font-size: 12px; height: 31px; line-height: 18px; color: #666; } .settings .weui-tab { height: auto; } .tips .page__desc { padding: 2px 5px; } .bottom { height: 44px; font-size: 12px; position: relative; } img.weui-tabbar__icon { width: 20px; height: 20px; cursor: pointer; } .changelogs { font-size: 14px; padding: 10px; text-align: left; line-height: 2.2; max-height: 300px; overflow-y: auto; } .loginNotice { font-size: 14px; padding: 10px 15px; text-align: left; line-height: 2.2; max-height: 300px; color: #5a5a5a; overflow-y: auto; } .loginNotice b { color: #2b902f; } #loginNotice .title { background: #fbf3a5; color: #ce7f66; padding-top: 0.8em; } #loginNotice a { color: #126700; } #loginNotice a:hover { color: #0c4600; } #loginNotice a.failed { color: #de4545; } #loginNotice.state-alive .title { background: #b9e684ad; color: #2b902f; padding-top: 0.8em; } #faqDialags .weui-dialog, #feedbackDialags .weui-dialog { background-color: #f8f8f8; height: 460px; } #specialEventDialags iframe, #faqDialags iframe, #feedbackDialags iframe { width: 90%; height: 450px; } #feedbackResult, #wechatDialags, #feedbackDialags, #faqDialags, #specialEventDialags, #loginNotice, #listenAudio { display: none; } .contents-box { margin-top: 0; } .listenVoice { text-align: left; } .messages-header { display: flex; border-bottom: 1px solid #ebeef5; height: 33px; padding-top: 0px; position: fixed; width: 54%; background: #fafafa; z-index: 10; } #order.weui-cells:after { border-bottom: none; } .messages-tab { position: relative; flex: 1; height: 48px; cursor: pointer; } .messages-tab span { width: 22px; height: 22px; background: #ccc; display: block; margin: 0 auto; } .messages-tab.selectedTab span { background: #4bc2ff; } .messages-tab span.notice { -webkit-mask: url(../image/notice.svg) no-repeat center; mask: url(../image/notice.svg) no-repeat center; -webkit-mask-size: 20px; mask-size: 20px; } .messages-tab span.coupon { -webkit-mask: url(../image/coupon.svg) no-repeat center; mask: url(../image/coupon.svg) no-repeat center; -webkit-mask-size: 22px; mask-size: 22px; } .messages-tab span.checkin { -webkit-mask: url(../image/checkin.svg) no-repeat center; mask: url(../image/checkin.svg) no-repeat center; -webkit-mask-size: 20px; mask-size: 20px; } .message-items { margin-top: 20px; } .message-items .weui-media-box { padding: 5px 15px; border-bottom: 1px solid #ebeef5; } .Button--link, .Button--plain { height: auto; padding: 0; line-height: inherit; border: none; border-radius: 0; } .messages-tabIcon { fill: #c2cfde; } .selectedTab .messages-tabIcon { fill: #0f88eb; } button.Button.messages-tab.Button--plain:focus { outline: none; } .selectedTab { background: #f5f5f5; border: 1px solid #e6e6e6; border-bottom: none; border-top: none; background-image: linear-gradient(0deg, #ffffff, #e6e6e64a); } .message i { padding-right: 5px; } .message .checkin_notice { background: url(../image/mileage.png) no-repeat; width: 20px; height: 20px; background-size: 20px; display: inline-block; margin-bottom: -3px; } .message .checkin_notice.coin { background-image: url(../image/coin.png); } .message .notice { background: url(../image/notice.png) no-repeat; width: 20px; height: 20px; background-size: 20px; display: inline-block; margin-bottom: -3px; } .coupon-box { position: relative; height: 50px; border: 1px solid #f2f2f2; background: #fff; display: inline-block; display: block; padding: 10px; } .coupon-box .price { padding: 1px 5px; color: #f23030; font-size: 15px; background: #fff4ec; display: inline-block; } .coupon-box a { font-size: 14px; color: #555; padding: 4px; } .reward { cursor: pointer; display: block; color: #d29737; } .reward h4 { padding-top: 10px; font-size: 22px; color: #4e4c4c; } .reward .qrcode { width: 210px; padding: 10px; } .reward .switch-tips { font-size: 14px; color: #ccc; } .other_actions .tips { color: #ccc; } .switch { cursor: pointer; color: #f54e4d; } .switch p { margin-top: -10px; } .switch .icon { margin-bottom: -5px; } #unreadCount { display: none; } .alipay_pay .redpack img { padding: 10px; } .switch-paymethod i { font-size: 21px; margin-top: -4px; } .weui-cell__bd .weui-icon-info-circle { margin-top: -4px; } #listenAudio .weui-cells { margin-bottom: .8em; } #listenAudio .weui-cell_access { cursor: pointer; } #changeLogs b { color: #4CAF50; } .text-tips { font-size: 12px; text-align: center; color: #666; padding-top: 5px; padding-bottom: 10px; } .recommendServices { text-align: center; } .recommendServices .el-tag { margin-right: 2px; } .openMobilePage { cursor: pointer; } .el-tag { background-color: rgba(64, 158, 255, .1); display: inline-block; padding: 0 10px; height: 32px; line-height: 30px; font-size: 12px; color: #409eff; border-radius: 4px; box-sizing: border-box; border: 1px solid rgba(64, 158, 255, .2); white-space: nowrap; } .el-tag a { color: #2196F3; } .el-tag--success { background-color: rgba(103, 194, 58, .1); border-color: rgba(103, 194, 58, .2); color: #67c23a; } .el-tag--success a { color: #67c23a; } .el-tag--warning { background-color: rgba(230, 162, 60, .1); border-color: rgba(230, 162, 60, .2); color: #e6a23c; } .el-tag--warning a { color: #e6a23c; } .el-tag--danger { background-color: hsla(0, 87%, 69%, .1); border-color: hsla(0, 87%, 69%, .2); color: #f56c6c; } .el-tag--danger a { color: #ff1d1d; } .bottom-box { height: 55px; border-top: #d4d4d4; background: #f7f7fa0a; position: relative; } .bottom-box .avatar { width: 30px; position: absolute; left: 10px; bottom: 11px; height: 30px; } .bottom-box .login-state { -webkit-mask: url(../image/avatar.svg) no-repeat center; -webkit-mask-size: 30px; mask: url(../image/avatar.svg) no-repeat center; mask-size: 30px; width: 30px; height: 30px; color: #dcdcdc; display: inline-block; cursor: pointer; background-color: #cecece; } .bottom-box .login-state.alive { background-color: #41bd2a; } .bottom-box .login-state.failed { background-color: #f56c6c; } .bottom-box .login-state.warning { background-color: #f7aa4d; } .bottom-box .links { right: 10px; position: absolute; bottom: 10px; } .links .text-tips { color: #bbb; padding-bottom: 5px; } .links .el-tag { padding: 0 8px; } .tips .weui-btn { display: none; } .tips a.weui-btn_primary.weui-btn:hover { color: #ffffffd6; } .showChangeLog { cursor: pointer; } .offline-icon { -webkit-mask: url(../image/offline.svg) no-repeat center; -webkit-mask-size: 22px; mask: url(../image/offline.svg) no-repeat center; mask-size: 22px; width: 22px; height: 22px; display: inline-block; background-color: #666; padding-right: 5px; margin-bottom: -4px; } .online-icon { -webkit-mask: url(../image/online.svg) no-repeat center; -webkit-mask-size: 22px; mask: url(../image/online.svg) no-repeat center; mask-size: 22px; width: 22px; height: 22px; background-color: #2b902f; padding-right: 5px; margin-bottom: -4px; display: none; } .request-permissions-icon.weui-icon-warn { color: #FFC107; font-size: 22px; cursor: pointer; } .state-alive .online-icon { display: inline-block; } .state-alive .offline-icon { display: none; } .el-tag--plus { border-color: #f7aa4d; background-color: #f9d2a3; } .el-tag--plus a { color: #da8d00; } .settings .weui-cells_form { margin-top: .8em; } .help_btns .el-tag a { font-size: 14px; padding: 12px; } .text-tips.version { padding-bottom: 0; cursor: pointer; } .loginNotice .detail { display: none; padding-top: 20px; } .loginNotice .detail h3 { text-align: center; } .unknown .status-icon { background-color: #ccc; } .alive .status-icon { background-color: #289e2d; } .alive .status-text { color: #289e2d; } .alive .weui-cell { background: #e6ffcad4; } .alive .weui-cell:hover { background: #d8f9b4; } .failed .weui-cell { background: #ffd3d3b5; } .failed .weui-cell:hover { background: #ffd9d9; } .failed .status-text { color: #de4545; } #login i { margin-top: -3px; } #know_more { text-align: center; padding-top: 10px; color: #999; cursor: pointer; } .update .weui-dialog .weui-dialog__bd { white-space: pre-line; text-align: left; padding-top: 1em; } .update .dismiss { cursor: pointer; float: right; margin-top: -13px; padding: 5px 10px; font-size: 22px; margin-right: -7px; } .showApplyAlipayCode { cursor: pointer; margin-top: 10px; color: #10aeff; } .apply-alipay-code .weui-dialog { border: 5px solid #10aeff; min-height: 400px; } .apply-alipay-code .weui-dialog__title { color: #10aeff; } .reward-tips { font-size: 12px; } .new-version { margin-left: 5px; margin-right: 5px; margin-top: -2px; padding: .2em .5em; } @media screen and (min-width: 352px) { .weui-dialog { width: 440px; } } ================================================ FILE: static/style/start.css ================================================ .start{ width: 640px; margin: 0 auto; } .page, body { background-color: var(--weui-BG-0); } .find-coupon{ width: 100%; border: 1px solid #ccc; margin: 12px 0; } ================================================ FILE: static/style/style.css ================================================ #teaclub { min-height: 80px; background: #f1fde347; padding: 10px; margin-bottom: 1em; } #teaclub .information-from{ font-size: 12px; color: #ccc; height: 12px; display: block; text-align: right; padding-top: 4px; padding-bottom: 4px; } #teaclub dt.metatit{ text-align: left; float: left; width: 66px; } .tb-wrap-newshop #teaclub dt.metatit{ width: 60px; } #teaclub .prop dd{ width: 420px; float: left; position: relative; overflow: visible; height: auto; z-index: 1; } .tb-wrap-newshop #teaclub .prop dd{ width: 400px; } #teaclub .clear:after { content: '\20'; display: block; height: 0; clear: both; } #Coupon-box{ margin-bottom: 1em; } a.teaclub-coupon:hover { text-decoration: none; } #teaclub .loading, #teaclub .coupon-not-found { background: #fff; font-size: 16px; text-align: center; color: #ccc; } #teaclub .loading img, #teaclub .coupon-not-found img { width: 80px; vertical-align: middle; } #teaclub .qrcode{ width: 60px; display: block; float: left; padding: 5px; } .teaclub-discount { padding: 10px; background-color: #FFF2E8; } .coupon-bonus-item { width: 100%; background-color: #ffe2e0; border-radius: 6px; display: block; max-width: 400px; } .coupon-bonus-item .coupon-item-left { width: 69%; color: #fff; background-color: #de1d3d; border-radius: 6px 0 0 6px; display: inline-block; } .coupon-bonus-item .coupon-item-right { text-align: right; padding-left: 7px; margin-top: 7px; color: #df4c47; display: inline-block; } .coupon-bonus-item .coupon-item-rmb { font-family: arial; font-size: 14px; line-height: 18px; text-align: right; padding-right: 12px; margin-top: 14px; } .coupon-bonus-item .coupon-item-surplus { font-size: 12px; line-height: 18px; text-align: right; padding-right: 12px; margin-top: 6px; } .coupon-bonus-item .coupon-item-rmb .rmb { font-family: tahoma; font-size: 28px; line-height: 18px; } #J_OtherDiscount .coupon-bonus-item { line-height: 1; margin-top: 20px; } #teaclub .PDD-card { -webkit-box-align: stretch; -ms-flex-align: stretch; align-items: stretch; overflow: hidden; position: relative; display: -webkit-box; display: -ms-flexbox; display: flex; min-height: 128px; max-width: 400px; border-radius: 6px; background-color: #f5f5f5; text-decoration: none; margin-bottom: 0.5em; } #teaclub .PDD-cardContainer { -webkit-box-align: stretch; -ms-flex-align: stretch; align-items: stretch; padding: 14px; box-sizing: border-box; display: -webkit-box; display: -ms-flexbox; display: flex; position: relative; width: 100%; z-index: 2; } #teaclub .PDD-imageContainer { border-radius: 0px; height: 100px; width: 100px; flex-shrink: 0; overflow: hidden; position: relative; } #teaclub .PDD-image { height: 100%; width: 100%; } #teaclub .PDD-qrcode { position: absolute; } #teaclub .PDD-cardContainer:hover .PDD-qrcode { z-index: 10; } #teaclub .PDD-info { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-flex: 1; -ms-flex-positive: 1; flex-grow: 1; margin-left: 12px; } #teaclub .PDD-titleText { line-height: 20px; max-height: 40px; color: #1a1a1a; font-size: 16px; line-height: 19px; font-weight: 600; font-synthesis: style; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } #teaclub .PDD-tool { margin-top: auto; -webkit-box-align: end; -ms-flex-align: end; align-items: flex-end; display: -webkit-box; display: -ms-flexbox; display: flex; } #teaclub .PDD-toolLeft { margin-right: auto; } #teaclub .PDD-price { -webkit-box-align: center; -ms-flex-align: center; align-items: center; color: #FF0036; display: -webkit-box; display: -ms-flexbox; display: flex; font-size: 16px; font-weight: 500; line-height: 18px; margin-right: auto; } #teaclub .PDD-button--plain.PDD-button--orange { color: #FF0036; } #teaclub .PDD-button--plain { -ms-flex-item-align: start; align-self: flex-start; font-size: 13px; height: 18px; font-weight: 600; font-synthesis: style; } #teaclub .PDD-button { -webkit-box-align: center; -ms-flex-align: center; align-items: center; display: -webkit-box; display: -ms-flexbox; display: flex; -ms-flex-negative: 0; flex-shrink: 0; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; } #teaclub-slides .slideshow-container { max-width: 1000px; position: relative; margin: auto; } #teaclub-slides .teaClubSlides { display: none; } /* Next & previous buttons */ #teaclub .prev, #teaclub .next { cursor: pointer; position: absolute; top: 50%; width: auto; margin-top: -22px; padding: 16px; color: white; font-weight: bold; font-size: 18px; transition: 0.6s ease; border-radius: 0 3px 3px 0; user-select: none; } /* Position the "next button" to the right */ #teaclub .next { right: 0; border-radius: 3px 0 0 3px; } /* On hover, add a black background color with a little bit see-through */ #teaclub .prev:hover, #teaclub .next:hover { background-color: rgba(0,0,0,0.8); } /* Caption text */ #teaclub .text { color: #f2f2f2; font-size: 15px; padding: 8px 12px; position: absolute; bottom: 8px; width: 100%; text-align: center; } /* Number text (1/3 etc) */ #teaclub .number-text { color: #f2f2f2; font-size: 12px; padding: 8px 12px; position: absolute; top: 0; } /* The dots/bullets/indicators */ #teaclub .dot { cursor: pointer; height: 15px; width: 15px; margin: 0 2px; background-color: #bbb; border-radius: 50%; display: inline-block; transition: background-color 0.6s ease; } #teaclub .active,#teaclub .dot:hover { background-color: #717171; } /* Fading animation */ #teaclub .fade { -webkit-animation-name: fade; -webkit-animation-duration: 1.5s; animation-name: fade; animation-duration: 1.5s; } @-webkit-keyframes fade { from {opacity: .4} to {opacity: 1} } @keyframes fade { from {opacity: .4} to {opacity: 1} } ================================================ FILE: webpack.config.js ================================================ const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const { EnvironmentPlugin } = require('webpack') function modifyManifest(buffer) { let manifest = JSON.parse(buffer.toString()); // make any modifications you like, such as if (process.env.VERSION) { manifest.version = process.env.VERSION; } // pretty print to JSON with two spaces manifest_JSON = JSON.stringify(manifest, null, 2); return manifest_JSON; } module.exports = { mode: process.env.NODE_ENV || 'development', entry: { background: './src/background.js', content_script: './src/content_script.js', start: './src/start.js', popup: './src/popup.js' }, output: { filename: 'static/[name].js', path: path.resolve(__dirname, 'dist') }, resolve: { alias: { vue: 'vue/dist/vue.runtime.esm.js' } }, node: { fs: 'empty' }, module: { rules: [ { test: /\.(png|jpg|gif)$/i, use: [ { loader: 'url-loader', loader: 'file-loader', options: { limit: 8192, outputPath: 'images', esModule: false }, }, ], }, { test: /\.svg$/, use: [ 'svg-url-loader', ] }, { test: /\.less$/, use: [ 'vue-style-loader', 'css-loader', 'less-loader' ] }, { test: /\.css$/i, use: ['style-loader', 'css-loader'], }, { test: /\.vue$/, loader: 'vue-loader' } ] }, plugins: [ new CleanWebpackPlugin(), new CopyPlugin([ { from: "public/manifest.json", to: "./manifest.json", transform(content, path) { return modifyManifest(content) } }, { from: 'public', to: '.' }, { from: 'static/image/icon', to: 'static/image' }, { from: 'node_modules/@sunoj/touchemulator/touch-emulator.js', to: 'static' }, { from: 'node_modules/zepto/dist/zepto.min.js', to: 'static' } ]), new VueLoaderPlugin(), new EnvironmentPlugin({ NODE_ENV: 'development', BROWSER: 'chrome', VERSION: '0.1.1', BUILDID: 0 }) ] };