嗨,大家好我是 Robin
今天要分享我前陣子一直想寫但是遲遲沒實作的小玩具,
那就是...
我想要我有一支程式可以每天幫我領麥當勞報報的優惠券!
我的一小步,是肥宅的一大步 - Robin
會有這想法的故事是這樣的,幾乎每週我都會與同事前往麥當勞,當然身為專業吃麥當勞的我一定會使用優惠券 Combo 連擊,而這個 Combo 券連擊一定要搭配麥當勞報報的優惠券。但是優惠券不是說拿就拿,必須每天領取,但是有時候過於忙碌會忘記領取,有一天去吃麥當勞的時候我發覺... 我發覺我無法使出我的優惠券 Combo 連擊。
身為一個肥宅...
慚愧阿!慚愧~
於是心生歹念想寫這個~
這篇的目標只有一個,就是...
下篇會再講肥宅的第二步,每天自動領取。
因為我還沒做... Orz
這篇原本在很早很早很早就開始寫(約莫兩週前),
但是一直都覺得哪裡寫不好哪裡不ok的(到現在也是xD)。
此篇主要不是
分享技術,
但是還是會有原始碼和該檔案的主要目的,
而是希望版上能有更多的這種為了更快學習一個語言或框架而發想寫一個專案~
感覺會讓寫程式在茫然的讀者能夠藉由寫專案獲得成就感。
進而讓自己的程式能力因為遇到問題而進步 !
就是一個寫不好沒關係!
滿足個人需求並且獲得成就感才是快樂學習的動力來源啊啊啊!
再進階一點就是滿足大眾需求~ 前提是要滿足的了自己...
先使用上篇使用的 Express-generator,建造一個骨架。
還沒看的可以先回去看~
結構大部分都是使用 Express-generator 所建制的,
除了 Controller
的部分是參照這位大大寫的自訂 MVC
的概念,想說可以順便熟悉 MVC
,如果內容觀念有誤再請多多見諒和留言告知我。
Node.js-Backend見聞錄(10):關於後端觀念(六)-關於MVC
.
├── LICENSE
├── README.md
├── app.js
├── bin
│ └── www
├── controllers
│ ├── lotteryController.js
│ └── userController.js
├── models
│ ├── lottery.js
│ ├── request.js
│ └── user.js
├── package-lock.json
├── package.json
├── public
│ └── stylesheets
│ └── style.css
├── routes
│ ├── lotteryRouter.js
│ └── usersRouter.js
├── test
│ ├── lottery.test.js
│ ├── schema
│ │ ├── lottery.json
│ │ └── user.json
│ └── user.test.js
└── views
├── error.pug
├── index.pug
└── layout.pug
const usersRouter = require('./routes/usersRouter');
const lotteryRouter = require('./routes/lotteryRouter');
app.use('/api/users', usersRouter);
app.use('/api/lottery', lotteryRouter);
這邊會有兩個
const express = require('express');
const lotteryController = require('../controllers/lotteryController');
const router = express.Router();
router.get('/', lotteryController.getLotteryStatus);
router.post('/', lotteryController.getLottery);
module.exports = router;
const express = require('express');
const lotteryController = require('../controllers/lotteryController');
const router = express.Router();
router.get('/', lotteryController.getLotteryStatus);
router.post('/', lotteryController.getLottery);
module.exports = router;
這邊分成兩部分
const lottery = require('../models/lottery');
async function getLottery(req, res) {
const nowDate = new Date();
nowDate.setDate(nowDate.getDate() + 2); // The lottery expire at the day after tomorrow.
const getLotteryResp = await lottery.getLottery(req.body.accessToken);
const getLotteryListResp = await lottery.getLotteryList(req.body.accessToken);
const stickerListResp = await lottery.getStickerList(req.body.accessToken);
const lotteryToday = await getLotteryListResp.body.results.coupons.filter(
(lotteryTarget) => lotteryTarget.object_info.redeem_end_datetime === nowDate.format('yyyy/mm/dd 23:59:59'),
);
const stickerToday = await stickerListResp.body.results.stickers.filter(
(stickerTarget) => stickerTarget.obtain_datetime > new Date().format('yyyy/mm/dd 00:00:00'),
);
const todayGet = (lotteryToday.length !== 0 && stickerToday.length === 0) ? lotteryToday[0].object_info.title : '歡樂貼QQ';
if (getLotteryResp.body.rc !== 1) {
res.status(getLotteryResp.statusCode);
res.json({
errorMessage: getLotteryResp.body.rm,
});
return;
}
res.status(getLotteryResp.statusCode);
res.json({
lottery: getLotteryResp.body.results.coupon.object_info.title,
todayLottery: todayGet,
});
}
async function getLotteryStatus(req, res) {
const lotteryList = [];
const getLotteryListResp = await lottery.getLotteryList(req.body.accessToken);
const stickerListResp = await lottery.getStickerList(req.body.accessToken);
if (getLotteryListResp.body.rc !== 1 && stickerListResp.body.rc !== 1) {
await res.status(400);
await res.json({
errorMessage: 'sticker or lottery have some problem ...',
});
return;
}
const lotteryNotExpire = await getLotteryListResp.body.results.coupons.filter((lotteryTarget) => lotteryTarget.object_info.redeem_end_datetime > new Date().format('yyyy/mm/dd HH:MM:ss'));
for (let i = 0; i < lotteryNotExpire.length; i += 1) {
lotteryList.push(lotteryNotExpire[i].object_info.title);
}
await res.status(200);
await res.json({
lottery: lotteryList,
totalStickersAmount: stickerListResp.body.results.stickers.length,
});
}
module.exports = {
getLottery,
getLotteryStatus,
};
const user = require('../models/user.js');
module.exports = async (req, res) => {
const resp = await user.getToken(req.body.account, req.body.password);
if (resp.body.rc !== '1') {
await res.status(resp.statusCode);
await res.json({
errorMessage: resp.body.rm,
});
return;
}
await res.status(resp.statusCode);
await res.json({ token: resp.body.results.member_info.access_token });
};
這邊分成三部分
const request = require('./request');
require('dotenv').config();
require('date.format');
const deviceTime = new Date().format('yyyy/mm/dd HH:MM:ss');
const source = {
app_version: process.env.APP_VERSION,
device_time: deviceTime,
device_uuid: process.env.DEVICE_UUID,
model_id: process.env.MODEL_ID,
os_version: process.env.OS_VERSION,
platform: process.env.PLATFORM,
};
async function getLottery(accessToken, sourceInfo = source) {
const option = {
url: `${process.env.MC_HOST}/lottery/get_item`,
json: {
access_token: accessToken,
source_info: sourceInfo,
},
};
const response = await request.postRequest(option);
return response;
}
async function getLotteryList(accessToken, sourceInfo = source) {
const option = {
url: `${process.env.MC_HOST}/coupon/get_list`,
json: {
access_token: accessToken,
source_info: sourceInfo,
},
};
const response = await request.postRequest(option);
return response;
}
async function getStickerList(accessToken, sourceInfo = source) {
const option = {
url: `${process.env.MC_HOST}/sticker/get_list`,
json: {
access_token: accessToken,
source_info: sourceInfo,
},
};
const response = await request.postRequest(option);
return response;
}
module.exports = {
getLottery,
getLotteryList,
getStickerList,
};
require('date.format');
const md5 = require('md5');
const request = require('./request');
async function getToken(userAccount, userPassword) {
const deviceTime = new Date().format('yyyy/mm/dd HH:MM:ss');
const appVersion = process.env.APP_VERSION;
const callTime = new Date().format('yyyymmddHHMMss');
const paramString = `${userAccount}${userPassword}`;
const modelId = process.env.MODEL_ID;
const osVersion = process.env.OS_VERSION;
const platform = process.env.PLATFORM;
const deviceUuid = process.env.DEVICE_UUID;
const orderNo = `${process.env.DEVICE_UUID}${callTime}`;
const maskMd5 = md5(`Mc${orderNo}${platform}${osVersion}${modelId}${deviceUuid}${deviceTime}${appVersion}${paramString}Donalds`);
const parm = {
account: userAccount,
password: userPassword,
OrderNo: orderNo,
mask: maskMd5,
source_info: {
app_version: appVersion,
device_time: deviceTime,
device_uuid: deviceUuid,
model_id: modelId,
os_version: osVersion,
Platform: process.env.PLATFORM,
},
};
const option = {
url: 'https://api.mcddaily.com.tw/login_by_mobile',
json: parm,
};
const response = await request.postRequest(option);
return response;
}
module.exports = {
getToken,
};
建立一個 .env 檔(注意這個設定檔如果有敏感資訊通常是不會上傳到 repo 的)
主要用於設定一些 device information
ACCESS_TOKEN,USER_ACCOUNT 和 USER_PASSWORD 主要是我用來寫測試的預設帳號,可以不必理會。
主要會被存取方式就是以 process.env.XXX
MC_HOST = https://api1.mcddailyapp.com
APP_VERSION = '2.2.0'
MODEL_ID = 'MIX 3'
OS_VERSION = '9'
PLATFORM = 'Android'
DEVICE_UUID = 'device_uuid'
USER_ACCOUNT = ''
USER_PASSWORD = ''
ACCESS_TOKEN = ''
這部分我選用最方便且最懶得方式
連結GitHub repositories 自動部署
aka 我推你就 deployee
前面的註冊登入就不教了連結給你~點我
建立你的 app
輸入 app 名稱(注意只能小寫) ,以及地區(只有美國和歐洲)。
選擇部署方式
連結並搜尋你的 repositories 名稱
設定自動部署(還可以選擇 branch)
打開你的 app 看一下 url 是什麼
打了發現...
在 Heroku 設定剛剛的設定檔
就是 Key value 的輸入這樣~
接著就...
可以正常運作了~
可以參閱此大大此篇詳細的 Charles 工具教學
mobile http request 攔截器攔起來-Charles
還可以偷看參考其他人用其他語言寫的麥當勞報報 xDD
McDaily
這邊這位大大還有貼心提醒
Please note that this app might stop working in the near future, as they have changed the hashing algorithm from simple MD5 to AES encryption + Base64 hashing using a differently formatted string.
專案持續更新,直到我做到完全滿足我的需求為止。
GitHub Link: mcdonaldLottery
目前窘境:
關於 API response time 目前的想法:
以上三種雲端伺服器都要實際跑過才會知道能否解決。
以上
歡迎留言或私訊討論~
感謝你
大推大推,
光看前文就一直很想看下去!!!
期待後續有更多好文!
哇~感謝你的鼓勵 xD 我會努力的