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 商店安装**(这样才能获得自动更新):
或者直接下载的 CRX文件手动安装(非常不建议)
*此方法通常只适用于 Chromium 内核的国产浏览器,因为 Chrome 出于安全原因已禁止通过除 Chrome 官方商店以外的其他渠道安装拓展。*
## 主要功能
* 自动查询当前浏览商品的渠道优惠券
* 自动签到领取飞猪里程
* 自动找拼多多低价同款
## 演示

## 重要提示
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
================================================
最近通知
{{unreadCount}}

枣树集惠
================================================
FILE: src/components/discounts.vue
================================================
{{discount.description}}
{{tag.name}}
{{discount.displayTime}}
优惠券
去购买
😭暂时没有近期优惠了
😭没有更多优惠信息了
暂时还没有关注任何标签
点击优惠信息中的标签可以筛选并关注标签哦
没有找到任何优惠
为了保证结果有效性,只展示近两周的优惠
商家自荐/优惠爆料可联系微信:cindywchat
================================================
FILE: src/components/events.vue
================================================
{{event.title}}
================================================
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(``)
} 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 ``
}
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
})
]
};