diff --git a/package.json b/package.json index 84e5e95..c35ef1f 100644 --- a/package.json +++ b/package.json @@ -14,16 +14,19 @@ "devDependencies": { "@types/dateformat": "^5.0.0", "@types/express": "^4.17.15", + "@types/mysql": "^2.15.21", "@types/node": "^18.11.18", "typescript": "^4.9.4" }, "dependencies": { "@line/bot-sdk": "^7.5.2", "dateformat": "^4.5.1", + "dayjs": "^1.11.7", "dotenv": "^16.0.3", "express": "^4.18.2", "fs": "^0.0.1-security", "linebot": "^1.6.1", + "mysql": "^2.18.1", "nodemon": "^2.0.20", "openai": "^3.1.0", "ts-node": "^10.9.1", diff --git a/src/Badminton.ts b/src/Badminton.ts new file mode 100644 index 0000000..f1a76e2 --- /dev/null +++ b/src/Badminton.ts @@ -0,0 +1,319 @@ +import * as line from "@line/bot-sdk"; +import dayjs from "dayjs"; +import DBTools from "./DBTools"; +import { NumberEx } from "./Engine/Number/NumberEx"; + +/** + * Badminton + */ +export default class Badminton { + + //#region Custom + + public static async Run(msg: string[]): Promise { + let message: line.Message | line.Message[] = null; + const instruction: string = msg[1]; + switch (instruction) { + case "查詢參加人員": { + const text: string = await this.searchPlay(+msg[2]); + message = { + type: "text", + text: text + }; + break; + } + + case "查詢對戰人員": { + message = await this.searchMatch(+msg[2]); + break; + } + + // case "查詢戰績": { + // const queryresp: any = await this.searchPlay(value); + // message.text = "Success"; + // break; + // } + + case "輸入名單": { + message = await this.inputList(msg); + break; + } + + default: + break; + } + return message; + } + + private static async searchPlay(date: number): Promise { + let resp: string = ""; + const query: string = String.Format("SELECT * FROM `badminton` WHERE `time` = '{0}' LIMIT 1;", date); + const queryresp: any[] = await DBTools.Query(query); + if (queryresp.length > 0) { + resp = queryresp[0].personnel; + } else { + resp = "這天沒有對戰資料"; + } + return resp; + } + + private static async searchMatch(date: number): Promise { + let resp: line.Message | line.Message[] = { + type: "text", + text: "這天沒有對戰資料" + }; + const query: string = String.Format("SELECT * FROM `badminton` WHERE `time` = '{0}' LIMIT 1;", date); + const queryresp: any[] = await DBTools.Query(query); + if (queryresp.length > 0) { + const matchData_Str: string = queryresp[0].battlecombination; + if (matchData_Str) { + const matchData: any = JSON.parse(matchData_Str); + resp = this.getMatch_LineFlex(date.toString(), matchData); + } + } + return resp; + } + + /** + * 輸入名單 + * @param msg + */ + private static async inputList(msg: any[]): Promise { + const date: number = +msg[2]; + let personnelDatas: any[] = []; + for (let i: number = 3; i < msg.length; i++) { + const personnelData: any[] = JSON.parse(msg[i]); + personnelDatas.push(personnelData); + } + const personnel_str: string = JSON.stringify(personnelDatas); + const query: string = String.Format("INSERT INTO `badminton` (time,personnel) VALUES ({0},'{1}') ON DUPLICATE KEY UPDATE personnel='{2}';" + , date, personnel_str, personnel_str); + await DBTools.Query(query); + return await this.listMatch(date, personnelDatas, personnel_str); + } + + private static async listMatch(date: number, personnelDatas: any[], personnel_str: string): Promise { + let man: string[] = []; + let woman: string[] = []; + for (let i: number = 0; i < personnelDatas.length; i++) { + const personnelData: any[] = personnelDatas[i]; + if (personnelData[0]) { + man.push(personnelData[1]); + } else { + woman.push(personnelData[1]); + } + } + // 打亂 + man.sort(function (): number { + return (0.5 - Math.random()); + }); + woman.sort(function (): number { + return (0.5 - Math.random()); + }); + let diffCount: number = Math.abs(NumberEx.minus(man.length, woman.length)); + if (man.length !== woman.length && diffCount >= 2) { + const needDiffCount: number = NumberEx.divide(diffCount, 2); + if (man.length > woman.length) { + for (let i: number = 0; i < needDiffCount; i++) { + woman.push(man.shift()); + } + } else { + for (let i: number = 0; i < needDiffCount; i++) { + man.push(woman.shift()); + } + } + } + const totalRound: number = 3; + const totalCount: number = man.length > woman.length ? man.length : woman.length; + let lastmatch: Object = {}; + let check: string[] = []; + let m_temp: number[] = []; + let w_temp: number[] = []; + for (let i: number = 0; i < totalCount; i++) { + m_temp.push(i); + w_temp.push(i); + } + for (let i: number = 0; i < totalRound; i++) { + const [match_num, check_temp] = this.getMatch(totalCount, check, m_temp, w_temp); + let match: string[][] = []; + check = check_temp; + for (let j: number = 0; j < match_num.length; j++) { + const [m, w] = match_num[j]; + const man_str: string = man[m] ?? "那個"; + const woman_str: string = woman[w] ?? "那個"; + match.push([man_str, woman_str]); + } + lastmatch[i] = match; + } + // console.log(`manCount: ${man.length}, womanCount: ${woman.length}`); + const result: string = JSON.stringify(lastmatch); + const query: string = String.Format("INSERT INTO `badminton` (time,personnel,battlecombination) VALUES ({0},'{1}','{3}') ON DUPLICATE KEY UPDATE battlecombination='{2}';" + , date, personnel_str, result, result); + // console.log(`query: ${query}`); + await DBTools.Query(query); + let message: line.Message | line.Message[] = this.getMatch_LineFlex(date.toString(), lastmatch); + return message; + } + + /** + * 取得名單 + * @param totalCount + * @param check + * @param m_temp + * @param w_temp + * @param repeatCount + */ + private static getMatch(totalCount: number, check: string[], m_temp: number[], w_temp: number[]): [number[][], string[]] { + const repeatMaxCount: number = 10; + let check_temp: string[] = [].concat(check); + const match: number[][] = []; + let m: number[] = [].concat(m_temp); + let w: number[] = [].concat(w_temp); + // 打亂 + m.sort(function (): number { + return (0.5 - Math.random()); + }); + w.sort(function (): number { + return (0.5 - Math.random()); + }); + for (let j: number = 0; j < totalCount; j++) { + for (let k: number = 0; k < repeatMaxCount; k++) { + const check_str: string = `m${m[0]}w${w[0]}`; + if (check.includes(check_str) && j !== totalCount - 1) { + w.sort(function (): number { + return (0.5 - Math.random()); + }); + continue; + } + check_temp.push(check_str); + match.push([m.shift(), w.shift()]); + break; + } + } + return [match, check_temp]; + } + + /** + * 取得名單Line模板 + * @param date + * @param matchs + */ + private static getMatch_LineFlex(date: string, matchs: Object): line.Message | line.Message[] { + const contents: any[] = []; + const dateTime_Str: string = `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`; + let dateTime: number = new Date(dateTime_Str).getTime(); + let date_Str: string = dayjs(Math.floor(dateTime)).format("YYYY-MM-DD"); + for (let i: number = 0, matchKey: string[] = Object.keys(matchs); i < matchKey.length; i++) { + const key: string = matchKey[i]; + const match: string[] = matchs[key]; + let boxs: any[] = []; + const bubble: any = { + "type": "bubble", + "size": "micro", + "body": { + "type": "box", + "layout": "vertical" + }, + "styles": { + "footer": { + "separator": true + } + } + }; + + const bubble_contents: Object = [ + { + "type": "text", + "text": "勝皇羽球團", + "weight": "bold", + "color": "#1DB446", + "size": "sm" + }, + { + "type": "text", + "text": date_Str, + "weight": "bold", + "size": "xl", + "margin": "sm" + }, + { + "type": "text", + "text": `第${i + 1}輪`, + "weight": "bold", + "size": "xxl", + "margin": "sm" + }, + { + "type": "separator", + "margin": "md" + }, + { + "type": "box", + "layout": "horizontal", + "contents": [ + { + "type": "text", + "text": "一號隊友", + "size": "sm", + "flex": 0 + }, + { + "type": "text", + "text": "二號隊友", + "size": "sm", + "align": "center" + } + ], + "margin": "md" + }, + { + "type": "separator", + "margin": "sm" + }, + { + "type": "box", + "layout": "vertical", + "contents": boxs, + "spacing": "sm", + "margin": "md" + }]; + for (let j: number = 0; j < match.length; j++) { + const box: any = { + "type": "box", + "layout": "horizontal", + "contents": [ + { + "type": "text", + "text": match[j][0], + "size": "sm", + "flex": 0 + }, + { + "type": "text", + "text": match[j][1], + "size": "sm", + "align": "center" + } + ] + }; + boxs.push(box); + } + bubble.body["contents"] = bubble_contents; + contents.push(bubble); + } + const messages: line.Message[] = [ + { + "type": "flex", + "altText": "對戰組合名單", + "contents": + { + "type": "carousel", + "contents": contents + } + } + ]; + return messages; + } + + //#endregion +} diff --git a/src/DBTools.ts b/src/DBTools.ts new file mode 100644 index 0000000..b5a717a --- /dev/null +++ b/src/DBTools.ts @@ -0,0 +1,44 @@ +import mysql from "mysql"; +import Tools from "./Tools"; + +/** + * DBTools + */ +export default class DBTools { + + //#region Custom + + public static async Query(query: string): Promise { + const conn: mysql.Connection = this.connect(); + + let resp: any = null; + let run: boolean = true; + conn.query(query, function (err: mysql.MysqlError, rows: any, fields: mysql.FieldInfo[]): void { + if (err) { + console.error(`${query} Error: \n${err.message}`); + run = false; + } + resp = rows; + run = false; + }); + while (run) { + await Tools.Sleep(100); + } + conn.end(); + return resp; + } + + private static connect(): mysql.Connection { + const conn: mysql.Connection = mysql.createConnection({ + host: process.env.DB_HOST, + port: +process.env.DB_PORT, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE + }); + conn.connect(); + return conn; + } + + //#endregion +} diff --git a/src/Engine/CCExtensions/ArrayExtension.ts b/src/Engine/CCExtensions/ArrayExtension.ts new file mode 100644 index 0000000..0dab41d --- /dev/null +++ b/src/Engine/CCExtensions/ArrayExtension.ts @@ -0,0 +1,116 @@ +declare interface Array { + /** + * 移除一個值並且回傳 + * @param index + */ + ExRemoveAt(index: number): T; + /** + * 移除全部值(注意. 參考的也會被清空) + * @example + * + * let bar: number[] = [1, 2, 3]; + * let bar2: number[] = bar; + * bar.Clear(); + * console.log(bar, bar2); + * + * // { + * // "bar": [], + * // "bar2": [] + * // } + */ + Clear(): void; + /** + * 物件陣列排序,asc&key陣列長度請一樣 + * PS. boolean 帶false是先true在false + * @link JavaScript Object 排序 http://www.eion.com.tw/Blogger/?Pid=1170#:~:text=JavaScript%20Object%20排序 + * @param asc 是否升序排列(小到大) + * @param key 需排序的key(優先順序左到右)(沒有就放空) + */ + ObjectSort(asc?: boolean[], key?: string[]): any[]; + /** + * 設計給ArrayforHoldButton使用 + * Add a non persistent listener to the UnityEvent. + * @param call Callback function. + */ + AddListener(call: Function): void; +} + +Array.prototype.ExRemoveAt || Object.defineProperty(Array.prototype, "ExRemoveAt", { + enumerable: false, + value: function (index: number): any { + let item: any = this.splice(index, 1); + return item[0]; + } +}); + +Array.prototype.Clear || Object.defineProperty(Array.prototype, "Clear", { + enumerable: false, + value: function (): void { + this.length = 0; + + // let foo: number[] = [1, 2, 3]; + // let bar: number[] = [1, 2, 3]; + // let foo2: number[] = foo; + // let bar2: number[] = bar; + // foo = []; + // bar.length = 0; + // console.log(foo, bar, foo2, bar2); + + // { + // "foo": [], + // "bar": [], + // "foo2": [ + // 1, + // 2, + // 3 + // ], + // "bar2": [] + // } + } +}); + +Array.prototype.ObjectSort || Object.defineProperty(Array.prototype, "ObjectSort", { + enumerable: false, + /** + * @param asc 是否升序排列(小到大) + * @param key 需排序的key(優先順序左到右)(沒有就放空) + */ + value: function (asc: boolean[] = [true], key?: string[]): any[] { + if (this.length === 0) { + return this; + } else if (!key || key.length === 0) { + console.error(`ObjectSort key error`); + return this; + } else if (asc.length !== key.length) { + console.error(`ObjectSort key asc error asc.length: ${asc.length}, key.length: ${key.length}`); + return this; + } + for (let i: number = 0; i < key.length; i++) { + const keyname: string = key[i]; + if (this[0][keyname] === undefined) { + console.error(`ObjectSort has not key[${i}]: ${keyname}`); + return this; + } + } + let count: number = key ? key.length : 1; + let arr: any[]; + for (let i: number = count - 1; i >= 0; i--) { + arr = this.sort(function (a: any, b: any): 1 | -1 { + let mya: any = a; + let myb: any = b; + if (key) { + mya = a[key[i]]; + myb = b[key[i]]; + } + + // 加個等於數字相同不要再去排序到 + if (asc[i]) { + return mya >= myb ? 1 : -1; + } else { + return mya <= myb ? 1 : -1; + } + }); + } + return arr; + } +}); \ No newline at end of file diff --git a/src/Engine/CCExtensions/CCExtension.ts.meta b/src/Engine/CCExtensions/CCExtension.ts.meta new file mode 100644 index 0000000..1ab1865 --- /dev/null +++ b/src/Engine/CCExtensions/CCExtension.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "b373f805-9297-4af5-8ea6-0a250649b5b0", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/src/Engine/CCExtensions/NumberExtension.ts b/src/Engine/CCExtensions/NumberExtension.ts new file mode 100644 index 0000000..5d39881 --- /dev/null +++ b/src/Engine/CCExtensions/NumberExtension.ts @@ -0,0 +1,189 @@ + +declare interface Number { + + /** + * 金額每三位數(千)加逗號, 並且補到小數點第2位 + * 輸出 41,038,560.00 + * @param precision 補到小數點第幾位 + * @param isPadZero 是否要補零 + * */ + ExFormatNumberWithComma(precision?: number, isPadZero?: boolean): string; + /** + * 基本4位數(9,999-999B-T) + * */ + ExTransferToBMK(precision?: number,offset?: number): string; + /** + * 數字轉字串, 頭補0 + * @param size + */ + Pad(size: number): string; + /** + * 四捨五入到小數點第X位 (同server計算規則) + * @param precision + */ + ExToNumRoundDecimal(precision: number): number; + /** + * 無條件捨去到小數點第X位 + * @param precision + */ + ExToNumFloorDecimal(precision: number): number; + /** + * 無條件捨去強制保留X位小數,如:2,會在2後面補上00.即2.00 + * @param precision 補到小數點第幾位 + * @param isPadZero 是否要補零 + */ + ExToStringFloorDecimal(precision: number, isPadZero?: boolean): string; + /** + * 取整數) + */ + ExToInt():number; + /** + * 小數轉整數(支援科學符號) + */ + Float2Fixed():number; + /** + * 數字長度(支援科學符號) + */ + DigitLength():number; + + target: number; + + +} + +Number.prototype.ExFormatNumberWithComma || Object.defineProperty(Number.prototype, 'ExFormatNumberWithComma', { + enumerable: false, + value: function (precision: number = 2, isPadZero: boolean = true) { + + // let arr = String(this).split('.'); + let arr = this.ExToStringFloorDecimal(precision, isPadZero).split('.'); + let num = arr[0], result = ''; + while (num.length > 3) { + result = ',' + num.slice(-3) + result; + num = num.slice(0, num.length - 3); + } + if (num.length > 0) result = num + result; + return arr[1] ? result + '.' + arr[1] : result; + } +}) + + +Number.prototype.ExTransferToBMK || Object.defineProperty(Number.prototype, 'ExTransferToBMK', { + enumerable: false, + value: function (precision: number=2,offset: number = 0) { + /**千 */ + let MONEY_1K: number = 1000; + /**萬 */ + // let MONEY_10K: number = 10000; + /**十萬 */ + // let MONEY_100K: number = 100000; + /**百萬 */ + let MONEY_1M: number = 1000000; + /**千萬 */ + // let MONEY_10M: number = 10000000; + /**億 */ + // let MONEY_100M: number = 100000000; + /**十億 */ + let MONEY_1B: number = 1000000000; + /**百億 */ + // let MONEY_10B: number = 10000000000; + /**千億 */ + // let MONEY_100B: number = 100000000000; + /**兆 */ + // let MONEY_1T: number = 1000000000000; + offset = Math.pow(10, offset); + // if (this >= MONEY_1T * offset) { + // //(3)1,000T + // //1T~ + // return (~~(this / MONEY_1T)).ExFormatNumberWithComma(0) + "T"; + // } + if (this >= MONEY_1B * offset) { + //1,000B~900,000B + //1B~900B + return (this / MONEY_1B).ExFormatNumberWithComma(3, false) + "B"; + } + else if (this >= MONEY_1M * offset) { + //1,000M~900,000M + //1M~900M + return (this / MONEY_1M).ExFormatNumberWithComma(3, false) + "M"; + } + else if (this >= MONEY_1K * offset) { + //1,000K~900,000K + //1K~90K + return (this / MONEY_1K).ExFormatNumberWithComma(3, false) + "K"; + } + else { + //0~9,000,000 + //0~9,000 + return this.ExFormatNumberWithComma(precision); + } + } +}) +Number.prototype.Pad || Object.defineProperty(Number.prototype, 'Pad', { + enumerable: false, + value: function (size: number) { + let s = this + ""; + while (s.length < size) s = "0" + s; + return s; + } +}) +Number.prototype.ExToNumRoundDecimal || Object.defineProperty(Number.prototype, 'ExToNumRoundDecimal', { + enumerable: false, + value: function (precision: number) { + return Math.round(Math.round(this * Math.pow(10, (precision || 0) + 1)) / 10) / Math.pow(10, (precision || 0)); + } +}) +Number.prototype.ExToInt || Object.defineProperty(Number.prototype, 'ExToInt',{ + enumerable: false, + value: function (){ + return ~~this; + } +}) +Number.prototype.ExToNumFloorDecimal || Object.defineProperty(Number.prototype, 'ExToNumFloorDecimal', { + enumerable: false, + value: function (precision: number) { + let str = this.toPrecision(12); + let dotPos = str.indexOf('.'); + return dotPos == -1 ? this : +`${str.substr(0, dotPos + 1 + precision)}`; + } +}) +Number.prototype.ExToStringFloorDecimal || Object.defineProperty(Number.prototype, 'ExToStringFloorDecimal', { + enumerable: false, + value: function (precision: number, isPadZero: boolean = true) { + // 取小數點第X位 + let f = this.ExToNumFloorDecimal(precision); + let s = f.toString(); + // 補0 + if (isPadZero) { + let rs = s.indexOf('.'); + if (rs < 0) { + rs = s.length; + s += '.'; + } + while (s.length <= rs + precision) { + s += '0'; + } + } + return s; + } +}) +Number.prototype.Float2Fixed || Object.defineProperty(Number.prototype, 'Float2Fixed', { + enumerable: false, + value: function () { + if (this.toString().indexOf('e') === -1) { + return Number(this.toString().replace('.', '')); + } + const dLen = this.DigitLength(); + return dLen > 0 ? +parseFloat((this * Math.pow(10, dLen)).toPrecision(12)) : this; + } +}) +Number.prototype.DigitLength || Object.defineProperty(Number.prototype, 'DigitLength', { + enumerable: false, + value: function () { + const eSplit = this.toString().split(/[eE]/); + const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0)); + return len > 0 ? len : 0; + } +}) + + \ No newline at end of file diff --git a/src/Engine/Number/NumberEx.ts b/src/Engine/Number/NumberEx.ts new file mode 100644 index 0000000..b9ad5e1 --- /dev/null +++ b/src/Engine/Number/NumberEx.ts @@ -0,0 +1,84 @@ +export module NumberEx { + + /** + * 检测数字是否越界,如果越界给出提示 + * @param {*number} num 输入数 + */ + function checkBoundary(num: number) { + if (_boundaryCheckingState) { + if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { + console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`); + } + } + } + + /** + * 精确乘法 + */ + export function times(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return times(times(num1, num2), others[0], ...others.slice(1)); + } + const num1Changed = num1.Float2Fixed(); + const num2Changed = num2.Float2Fixed(); + const baseNum = num1.DigitLength() + num2.DigitLength(); + const leftValue = num1Changed * num2Changed; + + checkBoundary(leftValue); + + return leftValue / Math.pow(10, baseNum); + } + + /** + * 精确加法 + */ + export function plus(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return plus(plus(num1, num2), others[0], ...others.slice(1)); + } + const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength())); + return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; + } + + /** + * 精确减法 + */ + export function minus(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return minus(minus(num1, num2), others[0], ...others.slice(1)); + } + const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength())); + return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; + } + + /** + * 精确除法 + */ + export function divide(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return divide(divide(num1, num2), others[0], ...others.slice(1)); + } + const num1Changed = num1.Float2Fixed(); + const num2Changed = num2.Float2Fixed(); + checkBoundary(num1Changed); + checkBoundary(num2Changed); + return times((num1Changed / num2Changed), Math.pow(10, num2.DigitLength() - num1.DigitLength())); + } + + /** + * 四舍五入 + */ + export function round(num: number, ratio: number): number { + const base = Math.pow(10, ratio); + return divide(Math.round(times(num, base)), base); + } + + let _boundaryCheckingState = false; + /** + * 是否进行边界检查 + * @param flag 标记开关,true 为开启,false 为关闭 + */ + function enableBoundaryChecking(flag = true) { + _boundaryCheckingState = flag; + } +} \ No newline at end of file diff --git a/src/Engine/Number/RandomEx.ts b/src/Engine/Number/RandomEx.ts new file mode 100644 index 0000000..1bfc080 --- /dev/null +++ b/src/Engine/Number/RandomEx.ts @@ -0,0 +1,90 @@ + +export module RandomEx { + + /** + * 取得隨機布林值 + */ + export function GetBool() { + return GetInt() >= 0; + } + /** + * 取得隨機整數(回傳min ~ max - 1) + * @param min + * @param max + */ + export function GetInt(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number { + return Math.floor(Math.random() * (max - min)) + min; + } + /** + * 取得隨機小數 + * @param min + * @param max + */ + export function GetFloat(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number { + return Math.random() * (max - min) + min; + } + /** + * 隨機取得複數個不重複回傳 + * @param num 取得數量 + * @param items 陣列 + */ + export function GetMultiNoRepeat(num: number, items: any[]): any[] { + let result: any[] = []; + for (let i: number = 0; i < num; i++) { + let ran: number = Math.floor(Math.random() * items.length); + let item = items.splice(ran, 1)[0]; + if (result.indexOf(item) == -1) { + result.push(item); + } + }; + return result; + } + + /** + * 根據權重取得複數個不重複回傳 + * @param prize 獎項 + * @param weights 機率 + * @param count 數量 + */ + export function GetMultiNoRepeatByWeight(prize: any[], weights: number[] = null, count: number = 1): any[] { + if (weights === null) { + weights = []; + for (let i: number = 0; i < prize.length; i++) { + weights.push(1); + } + } + let target: any[] = []; + for (let i: number = 0; i < count; i++) { + let results: number[] = RandomEx.GetPrizeByWeight(prize, weights); + prize.splice(results[0], 1); + weights.splice(results[0], 1); + target.push(results[1]); + } + return target; + } + + + /** + * 根據權重隨機取值 + * @param prize 獎項 + * @param weights 機率 + */ + export function GetPrizeByWeight(prize: any[], weights: number[]): any[] { + if (prize.length !== weights.length) { + console.error(`GetWeight error -> prize.length:${prize.length} !== weights.length:${weights.length}`); + return null; + } + let totalWeight: number = 0; + for (let i: number = 0; i < weights.length; i++) { + totalWeight += weights[i]; + } + let random: number = RandomEx.GetInt(0, totalWeight) + 1; + let nowWeight: number = weights[0]; + for (let i: number = 0; i < weights.length; i++) { + if (nowWeight >= random) { + return [i, prize[i]]; + } + nowWeight += weights[i + 1]; + } + } +} diff --git a/src/Engine/String.ts b/src/Engine/String.ts new file mode 100644 index 0000000..33b622e --- /dev/null +++ b/src/Engine/String.ts @@ -0,0 +1,16 @@ +interface StringConstructor { + IsNullOrEmpty: (value: string) => boolean; + Format: (format: string, ...args: any[]) => string; +} + +String.IsNullOrEmpty = function (value: string): boolean { + return value === undefined || value === null || value.trim() === ""; +}; + +String.Format = function (format: string, ...args: any[]): string { + return format.replace(/{(\d+)}/g, (match, index) => { + let value: any = args[index]; + if (value === null || value === undefined) { return ""; } + return "" + value; + }); +}; diff --git a/src/MessageClass.ts b/src/MessageClass.ts index 1213eba..6bbf80e 100644 --- a/src/MessageClass.ts +++ b/src/MessageClass.ts @@ -1,4 +1,5 @@ import * as line from "@line/bot-sdk"; +import Badminton from "./Badminton"; import OpenAI from "./OpenAI"; /** @@ -68,8 +69,8 @@ export default class MessageClass { } public async User(event: any): Promise { - let userId = event.source.userId; - let replyMsg = event.message.text; + let userId: string = event.source.userId; + let replyMsg: string = event.message.text; // let displayName = ""; // let profile = await this.bot.getProfile(userId); // if (profile) { @@ -77,61 +78,34 @@ export default class MessageClass { // } // 豬喵 特別功能 if ([process.env.toJianMiau, process.env.toZhuHan].includes(userId)) { - const response: string = await OpenAI.RunOpenAI(replyMsg); - replyMsg = response; + /** 訊息 */ + let msg: string[] = event.message.text.split(" "); + + if (userId === process.env.toJianMiau) { + // 建喵 指令功能 + /** 指令 */ + let instruction: string = msg[0]; + switch (instruction) { + case "羽球": { + const messages: line.Message | line.Message[] = await Badminton.Run(msg); + if (messages) { + return this.bot.replyMessage(event.replyToken, messages); + } + } + + default: { + const response: string = await OpenAI.RunOpenAI(replyMsg); + replyMsg = response; + break; + } + } + } else { + // 豬涵 指令功能 + const response: string = await OpenAI.RunOpenAI(replyMsg); + replyMsg = response; + } } - // /** 訊息 */ - // let Msg = event.message.text.split(" "); - // /** 指令 */ - // let Instruction = Msg[0]; - // switch (Instruction) { - // case "msg": - // case "Msg": - // case "MSG": { - // if (userId == process.env.toJianMiau) { - // replyMsg = ""; - // if (Msg[1] === "豬涵") { - // if (Msg[2] === "豬涵") { - // Msg[2] = process.env.toZhuHantoZhuHan; - // } else if (Msg[2] === "建喵") { - // Msg[2] = process.env.toZhuHantoJianMiau; - // } - // for (let i = 3; i < Msg.length; i++) { - // replyMsg += Msg[i] + (i === Msg.length - 1 ? "" : " "); - // } - // let res_Msg = this.ZhuHanbot.push(Msg[2], replyMsg); - // } else { - // for (let i = 3; i < Msg.length; i++) { - // replyMsg += Msg[i] + (i === Msg.length - 1 ? "" : " "); - // } - // let res_Msg = this.bot.push(Msg[2], replyMsg); - // } - - // let ToJM_message = "已發送訊息:"; - // ToJM_message += `\nMyId: ${Msg[1]}`; - // ToJM_message += `\nuserId: ${Msg[2]}`; - // ToJM_message += `\nmessage: ${replyMsg}`; - // let res_reply = event.reply(ToJM_message).then(function (data) { - // // 當訊息成功回傳後的處理 - // }).catch(function (error) { - // // 當訊息回傳失敗後的處理 - // }); - // } - // break; - // } - - // default: { - // // 使用event.reply(要回傳的訊息)方法可將訊息回傳給使用者 - // event.reply(replyMsg).then(function (data) { - // // 當訊息成功回傳後的處理 - // }).catch(function (error) { - // // 當訊息回傳失敗後的處理 - // }); - // break; - // } - // } - // } return this.bot.replyMessage(event.replyToken, { type: "text", text: replyMsg @@ -142,6 +116,29 @@ export default class MessageClass { // }); } + public async Group(event: any): Promise { + let groupId: string = event.source.groupId; + // 羽球 特別功能 + if ([process.env.toBadminton].includes(groupId)) { + /** 訊息 */ + let msg: string[] = event.message.text.split(" "); + /** 指令 */ + let instruction: string = msg[0]; + switch (instruction) { + case "羽球": { + const messages: line.Message | line.Message[] = await Badminton.Run(msg); + if (messages) { + return this.bot.replyMessage(event.replyToken, messages); + } + } + + default: { + break; + } + } + } + } + // Group(event) { // switch (event.source.groupId) { // case process.env.toYoutube: { diff --git a/src/OpenAI.ts b/src/OpenAI.ts index 1449a1e..faad549 100644 --- a/src/OpenAI.ts +++ b/src/OpenAI.ts @@ -8,13 +8,14 @@ export default class OpenAI { //#region Custom public static async RunOpenAI(msg: string): Promise { - const configuration = new Configuration({ + const configuration: Configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, }); - const openai = new OpenAIApi(configuration); + const openai: OpenAIApi = new OpenAIApi(configuration); + // tslint:disable-next-line:typedef const response = await openai.createCompletion({ model: "text-davinci-003", - prompt: msg, + prompt: "人類:" + msg, temperature: 0.7, max_tokens: 256, top_p: 1, @@ -22,8 +23,8 @@ export default class OpenAI { presence_penalty: 0, }); let resptext: string = msg; - const choices = response.data.choices; - for (let i = 0; i < choices.length; i++) { + const choices: CreateCompletionResponseChoicesInner[] = response.data.choices; + for (let i: number = 0; i < choices.length; i++) { const choice: CreateCompletionResponseChoicesInner = choices[i]; resptext += choice.text; } diff --git a/src/Tools.ts b/src/Tools.ts new file mode 100644 index 0000000..ab7e94e --- /dev/null +++ b/src/Tools.ts @@ -0,0 +1,13 @@ +/** + * Tools + */ +export default class Tools { + + //#region Custom + + public static Sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + //#endregion +} diff --git a/src/app.ts b/src/app.ts index 37fe0c5..e77180d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,8 +3,14 @@ // 監聽檔案變化 "npm start" // 連線Debug "npm run dev" +import dayjs from "dayjs"; +import "dayjs/locale/zh-tw"; import dotenv from "dotenv"; +import "./Engine/CCExtensions/ArrayExtension"; +import "./Engine/CCExtensions/NumberExtension"; +import "./Engine/String"; import LineBotClass from "./LineBotClass"; +dayjs.locale("zh-tw"); dotenv.config(); new LineBotClass(); \ No newline at end of file