From 8f0b08f3a7e778a57348cdded8e58dd937e1220a Mon Sep 17 00:00:00 2001 From: JianMiau Date: Sat, 24 Apr 2021 21:21:23 +0800 Subject: [PATCH] =?UTF-8?q?[add]=20=E5=88=9D=E5=A7=8B=E8=B3=87=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 ++ CPBLClass.js | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++ app.js | 96 +++++++++++++++++ package.json | 24 +++++ 4 files changed, 409 insertions(+) create mode 100644 .gitignore create mode 100644 CPBLClass.js create mode 100644 app.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb852c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +.env +package-lock.json +*.pem +.foreverignore +.vscode \ No newline at end of file diff --git a/CPBLClass.js b/CPBLClass.js new file mode 100644 index 0000000..a9ed982 --- /dev/null +++ b/CPBLClass.js @@ -0,0 +1,283 @@ +const dateFormat = require('dateformat'); +const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; +const cheerio = require('cheerio'); +const { decode } = require('querystring'); +const tools = require('../tools/tools'); + +/** CPBL */ +class CPBLClass { + // constructor(bot, JianMiaubot, Tools_MYSQLDB) { + // this.bot = bot; + // this.JianMiaubot = JianMiaubot; + // this.Tools_MYSQLDB = Tools_MYSQLDB; + // } + + async GetCPBL(data) { + let url = data["URL"]; + let Response = await this.GetData(url); + return new Promise((resolve, reject) => { + // 傳入 resolve 與 reject,表示資料成功與失敗 + resolve(this.ParseCPBL(Response)); + // reject() + }); + } + + async GetCPBLList(data) { + // let url = "http://www.cpbl.com.tw/schedule/index"; + let datetime = dateFormat(new Date(), "yyyymmdd"); + let year = datetime.substr(0, 4); + let month = datetime.substr(4, 2); + let day = datetime.substr(6, 2); + let date = data["Date"]; + let url = `http://www.cpbl.com.tw/schedule/index/${year}-${month}-${day}.html?&date=${year}-${month}-${day}&gameno=01&sfieldsub=&sgameno=01`; + let Response = await this.GetData(url); + return new Promise((resolve, reject) => { + // 傳入 resolve 與 reject,表示資料成功與失敗 + resolve(this.ParseCPBLList(Response, date)); + // reject() + }); + } + + ParseCPBL(htmlString) { + let index_obj = { + "1上": 0, + "1下": 1, + "2上": 2, + "2下": 3, + "3上": 4, + "3下": 5, + "4上": 6, + "4下": 7, + "5上": 8, + "5下": 9, + "6上": 10, + "6下": 11, + "7上": 12, + "7下": 13, + "8上": 14, + "8下": 15, + "9上": 16, + "9下": 17, + }; + // htmlString = htmlString.replace('/\\r\\n/g', '').replace('/\\t/g', '').replace('/\\\"/g', '"'); + // let htmlString1 = htmlString.replace(/\r\n|\n|\t/g, ""); + // htmlString1 = htmlString1.replace(/\\/g, ''); + // let htmlString1 = ''; + // const html = document.createElement('html'); + // html.innerHTML = htmlString; + // const title = document.getElementsByTagName('title')[0].innerText // 取得title:我的標題 + // 把 body 放進 cheerio 準備分析 + const $ = cheerio.load(htmlString); // 載入 body + const result = []; // 建立一個儲存結果的容器 + // const table_tr = $("tbody tr"); // 爬最外層的 Table(class=BoxTable) 中的 tr + const table_tr = $(".gap_b20 table tbody tr"); // 爬最外層的 Table(class=BoxTable) 中的 tr + + let index = 0; + let _result = JSON.parse("[]"); + for (let i = 0; i < table_tr.length; i++) { // 走訪 tr + const table_td = table_tr.eq(i).find('td'); // 擷取每個欄位(td) + const table_th = table_tr.eq(i).find('th'); // 擷取每個欄位(td) + let title = table_td.eq(1).text(); // time (台灣時間) + if (!title) { + title = table_th.eq(0).text(); + } + // const latitude = table_td.eq(2).text(); // latitude (緯度) + // const longitude = table_td.eq(3).text(); // longitude (經度) + // const amgnitude = table_td.eq(4).text(); // magnitude (規模) + // const depth = table_td.eq(5).text(); // depth (深度) + // const location = table_td.eq(6).text(); // location (位置) + // const url = table_td.eq(7).text(); // url (網址) + // 建立物件並(push)存入結果 + // result.push(Object.assign({ time, latitude, longitude, amgnitude, depth, location, url })); + if (title) { + if (table_th.eq(0).text()) { + index = index_obj[title]; + } + _result[index] = _result[index] ? _result[index] : []; + _result[index].push(Object.assign({ title })); + result.push(Object.assign({ title })); + } + } + // request({ + // url: "http://www.cpbl.com.tw/games/play_by_play.html?&game_type=01&game_id=62&game_date=2021-04-18&pbyear=2021", // 中央氣象局網頁 + // method: "GET" + // }, function (error, response, body) { + // if (error || !body) { + // return; + // } + // const $ = cheerio.load(body); // 載入 body + // const result = []; // 建立一個儲存結果的容器 + // // const table_tr = $("tbody tr"); // 爬最外層的 Table(class=BoxTable) 中的 tr + // const table_tr = $(".gap_b20 table tbody tr"); // 爬最外層的 Table(class=BoxTable) 中的 tr + + // for (let i = 0; i < table_tr.length; i++) { // 走訪 tr + // const table_td = table_tr.eq(i).find('td'); // 擷取每個欄位(td) + // const table_th = table_tr.eq(i).find('th'); // 擷取每個欄位(td) + // let title = table_td.eq(1).text(); // time (台灣時間) + // if (!title) { + // title = table_th.eq(0).text(); + // } + // // const latitude = table_td.eq(2).text(); // latitude (緯度) + // // const longitude = table_td.eq(3).text(); // longitude (經度) + // // const amgnitude = table_td.eq(4).text(); // magnitude (規模) + // // const depth = table_td.eq(5).text(); // depth (深度) + // // const location = table_td.eq(6).text(); // location (位置) + // // const url = table_td.eq(7).text(); // url (網址) + // // 建立物件並(push)存入結果 + // // result.push(Object.assign({ time, latitude, longitude, amgnitude, depth, location, url })); + // if (title) { + // result.push(Object.assign({ title })); + // } + // } + // // 在終端機(console)列出結果 + // console.log("result"); + // // 寫入 result.json 檔案 + // // fs.writeFileSync("result.json", JSON.stringify(result)); + // }); + // $('.gap_b20 .std_tb mix_line gap_t10 overwrite_size_15 tbody tr').each(function (i, elem) { + // weathers.push($(this).text().split('\n')); + // }); + return JSON.stringify(_result); + } + + ParseCPBLList(htmlString, date) { + let month = +dateFormat(new Date(), "mm") - 1; + const $ = cheerio.load(htmlString); // 載入 body + const result = {}; // 建立一個儲存結果的容器 + let map = new Map(); + let set = 0; + let get = 0; + // const table_tr = $("tbody tr"); // 爬最外層的 Table(class=BoxTable) 中的 tr + // const table_tr = $(".gap_b20 table tbody tr"); // 爬最外層的 Table(class=BoxTable) 中的 tr + const table_tr = $(".schedule.gap_t20.gap_b20 tbody").children().first().siblings("tr"); + table_tr.splice(0, 0, $(".schedule.gap_t20.gap_b20 tbody").children().first()[0]); + // const aaa = table_tr.children().first().nextAll(); + // const aaa = table_tr.find('.one_block'); + // const bbb = aaa.eq(0).attr('onclick'); + let index = 0; + // let _result = JSON.parse("[]"); + // for (let i = 0; i < table_tr.length; i++) { // 走訪 tr + // const table_td = table_tr.eq(i).find('td'); // 擷取每個欄位(td) + // const table_th = table_tr.eq(i).find('th'); // 擷取每個欄位(td) + // let address = table_td.eq(1).text(); // time (台灣時間) + // let img1 = table_td.eq(0).find('img').attr('src'); // time (台灣時間) + // let img2 = table_td.eq(2).find('img').eq(0).attr('src'); // time (台灣時間) + // if (!address) { + // address = table_th.eq(0).text(); + // } + // if (address) { + // // if (table_th.eq(0).text()) { + // // index = index_obj[title]; + // // } + // // _result[index] = _result[index] ? _result[index] : []; + // // _result[index].push(Object.assign({ title })); + // result.push(Object.assign({ title: address })); + // } + // } + for (let i = 0; i < table_tr.length; i++) { + let table = []; + let td = table_tr.eq(i).children().first().siblings("td"); + let th = table_tr.eq(i).children().first().siblings("th"); + if (table_tr[i].attribs.class === "day") { + th.splice(0, 0, table_tr.eq(i).children().first()[0]); + table = th; + } else if (th.length > 0) { + th.splice(0, 0, table_tr.eq(i).children().first()[0]); + for (let j = 0; j < th.length; j++) { + let index = th.eq(j).text(); + if (+index === 1) { + month++; + } + index = "2021" + tools.padLeft(month, 2) + index; + result[index] = []; + map.set(set, index); + set++; + } + table = th; + } else if (td.length > 0) { + td.splice(0, 0, table_tr.eq(i).children().first()[0]); + for (let j = 0; j < td.length; j++) { + // let div = td.eq(j).children().first().siblings("div"); + let div = td.eq(j).find("div"); + if (div.length > 0) { + for (let k = 0; k < div.length; k++) { + let table1 = div.eq(k).children().first().siblings("table"); + table1.splice(0, 0, div.eq(k).children().first()[0]); + for (let l = 0; l < table1.length; l++) { + if (table1[l].attribs.class === "schedule_team") { + let date = map.get(get); + let schedule_team = table1.eq(0).find('td'); + let img1 = schedule_team.eq(0).find('img').attr('src'); + let address = schedule_team.eq(1).text(); + let img2 = schedule_team.eq(2).find('img').attr('src'); + let schedule_info3 = table1.eq(3).find('td'); + let time = schedule_info3.eq(1).text(); + let href = {}; + if (!time) { + href = decode(div[k].attribs.onclick.split("?")[1]); + } else { + let schedule_info1 = table1.eq(1).find('th'); + let game_type = "01"; + let game_id = schedule_info1.eq(1).text(); + let game_date = `${date.substr(0, 4)}-${date.substr(4, 2)}-${date.substr(6, 2)}`; + let pbyear = date.substr(0, 4); + href = { + time: schedule_info3.eq(1).text(), + game_type: game_type, + game_id: game_id, + game_date: game_date, + pbyear: pbyear + }; + } + let Data = { + img1: img1, + address: address, + img2: img2, + href: href + }; + result[date].push(Data); + } + } + } + } + get++; + } + table = td; + } + // for (let j = 0; j < table.length; j++) { + // let address = table.eq(j).text(); + // if (address) { + // result.push(Object.assign({ title: address })); + // } + // } + } + return JSON.stringify(result[date]); + } + + + /** + * 取得表 + * @param Url Url + * @param arrange 是否需要整理 + */ + GetData(Url) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status >= 200 && xhr.status < 400) { + var response = xhr.responseText; + resolve(response); + } else { + reject(); + } + } + }; + xhr.open("GET", Url, true); + xhr.send(); + }); + } + +} + +module.exports = CPBLClass \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..866e01e --- /dev/null +++ b/app.js @@ -0,0 +1,96 @@ +// 背景執行 forever start -w -a -l api.log app.js +// 監聽檔案變化 nodemon app.js +// npm start +// npm run dev +// Debug nodemon --inspect=192.168.168.15:9229 app.js + +const dateFormat = require('dateformat'); +require('dotenv').config() +const http = require('http'); +const https = require('https'); +const fs = require('fs'); +const express = require('express'); +const app = express(); + +//讀取憑證及金鑰 +const prikey = fs.readFileSync('privkey.pem', 'utf8'); +const cert = fs.readFileSync('cert.pem', 'utf8'); +const cafile = fs.readFileSync('chain.pem', 'utf-8'); + +//建立憑證及金鑰 +const credentials = { + key: prikey, + cert: cert, + ca: cafile +}; + +const CPBLClass = require('./CPBLClass'); +const { decode } = require('querystring'); +const CPBL = new CPBLClass(); + +const port = process.env.PORT || 3000; + +const server = https.createServer(credentials, async function (req, res) { + // const server = http.createServer(async function (req, res) { + // console.log(`rawBody: ${req.rawBody}`); + // res.writeHead(200); + if (req.method === 'POST') { + // console.log(`body: ${JSON.stringify(req.body)}`); + let Request = req.url.replace("/", ""); + let Response = ""; + let data = ''; + req.on('data', chunk => { + data += chunk; + }); + req.on('end', async () => { + switch (req.headers["content-type"]) { + case "application/x-www-form-urlencoded": { + data = decode(data); + // data = [] + // let strs = str.split("&"); + // for (let i = 0; i < strs.length; i++) { + // data[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]); + // } + break; + } + + case "application/json": { + data = JSON.parse(data); + break; + } + + default: + break; + } + switch (Request) { + case "CPBL": + Response = await CPBL.GetCPBL(data); + break; + + case "CPBLList": + Response = await CPBL.GetCPBLList(data); + break; + + default: + break; + } + res.writeHead(200); + res.write(Response); + return res.end(); + }); + // } else if (req.method === 'GET' && req.url === path) { + // let Response = await CPBL.GetCPBL(); + // res.writeHead(200); + // res.write(Response); + // return res.end(); + } else { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + return res.end('Not found'); + } +}); +server.listen(port, function () { + let datetime = dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss"); + console.log(`${datetime} listening on ${port}`); + console.log(`${datetime} [api已準備就緒]`); +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..f6f366c --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "cpbl", + "version": "1.0.0", + "description": "", + "main": "app.js", + "scripts": { + "start": "forever start -a -l api.log app.js", + "dev": "nodemon --inspect=127.0.0.1:9229 app.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "cheerio": "^1.0.0-rc.6", + "dateformat": "^4.5.1", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "http": "0.0.1-security", + "https": "^1.0.0", + "node-html-parser": "^3.1.5", + "nodemon": "^2.0.7", + "request": "^2.88.2", + "xmlhttprequest": "^1.8.0" + } +} \ No newline at end of file