mirror of
https://github.com/cloudmaker97/TikTok-mGBA-Emulator.git
synced 2025-12-06 07:58:38 +00:00
Der Prototyp mit Datenbankanbindung für das Speichern von Geschenkwerten (für die freien Züge)
This commit is contained in:
parent
f14ac03059
commit
8a65ce78a5
20 changed files with 1243 additions and 41 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -6,5 +6,4 @@
|
||||||
/node/dist
|
/node/dist
|
||||||
|
|
||||||
# Ignore everything except save files
|
# Ignore everything except save files
|
||||||
/roms/*
|
/storage/*.sqlite3
|
||||||
!/roms/*.sav
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.12.11",
|
"@types/node": "^20.12.11",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
"swagger-typescript-api": "^13.0.3",
|
"swagger-typescript-api": "^13.0.3",
|
||||||
"tiktok-live-connector": "^1.1.6"
|
"tiktok-live-connector": "^1.1.6"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
1000
node/pnpm-lock.yaml
1000
node/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,35 +1,59 @@
|
||||||
import { MgbaHttp } from "../../classes/gba-api/MgbaHttp";
|
import { MgbaHttp } from "../modules/gba-api/MgbaHttp";
|
||||||
import { Message } from "../../classes/tiktok/Message";
|
import { Message } from "../modules/tiktok/Message";
|
||||||
import { WebcastPushConnection } from "tiktok-live-connector";
|
import { WebcastPushConnection } from "tiktok-live-connector";
|
||||||
import { User, FollowerRole } from "../../classes/tiktok/User";
|
import { User, FollowerRole } from "../modules/tiktok/User";
|
||||||
import { Gift } from "../tiktok/Gift";
|
import { Gift } from "../modules/tiktok/Gift";
|
||||||
|
import { Logger, LogLevel } from "../modules/logging/Logging";
|
||||||
|
import { UserStorage } from "../modules/database/Storage";
|
||||||
|
|
||||||
|
const FREE_GAME_INPUT_MOVES = 5;
|
||||||
|
|
||||||
export class GameController {
|
export class GameController {
|
||||||
private channelName: string;
|
private channelName: string;
|
||||||
private users: Map<string, User> = new Map<string, User>();
|
private users: Map<string, User> = new Map<string, User>();
|
||||||
private webcastPushConnection: WebcastPushConnection;
|
private webcastPushConnection: WebcastPushConnection;
|
||||||
private mgbaHttp: MgbaHttp = new MgbaHttp({});
|
private mgbaHttpClient: MgbaHttp = new MgbaHttp({});
|
||||||
|
private userStorage: UserStorage;
|
||||||
private availableGifts: Map<number, Gift> = new Map<number, Gift>();
|
private availableGifts: Map<number, Gift> = new Map<number, Gift>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new GameController object
|
||||||
|
* @param {string} channelName The channel name for the TikTok Live connection
|
||||||
|
*/
|
||||||
constructor(channelName: string) {
|
constructor(channelName: string) {
|
||||||
|
// Initialize the UserStorage object
|
||||||
|
this.userStorage = new UserStorage();
|
||||||
|
// Set the channel name and create a new WebcastPushConnection
|
||||||
this.channelName = channelName;
|
this.channelName = channelName;
|
||||||
this.mgbaHttp = new MgbaHttp({
|
|
||||||
baseUrl: "http://localhost:5000",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.webcastPushConnection = new WebcastPushConnection(
|
this.webcastPushConnection = new WebcastPushConnection(
|
||||||
this.channelName
|
this.channelName
|
||||||
);
|
);
|
||||||
|
// Set the base URL for the mGBA API client
|
||||||
this.getWebcastPushConnection().getAvailableGifts().then((gifts) => {
|
this.mgbaHttpClient = new MgbaHttp({
|
||||||
gifts.forEach((gift: any) => {
|
baseUrl: "http://localhost:5000",
|
||||||
this.availableGifts.set(gift.id, new Gift({
|
|
||||||
giftId: gift.id,
|
|
||||||
costDiamonds: gift.diamond_count,
|
|
||||||
name: gift.name,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
// Load the available gifts from the TikTok platform and start the GameController
|
||||||
|
this.getAvailableGifts().then((gifts) => {
|
||||||
|
this.availableGifts = gifts;
|
||||||
|
this.startGameController();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the available gifts from the TikTok platform
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
private async getAvailableGifts(): Promise<Map<number, Gift>> {
|
||||||
|
let returnGifts = new Map<number, Gift>();
|
||||||
|
let gifts = await this.getWebcastPushConnection().getAvailableGifts();
|
||||||
|
gifts.forEach((gift: any) => {
|
||||||
|
returnGifts.set(gift.id, new Gift({
|
||||||
|
giftId: gift.id,
|
||||||
|
costDiamonds: gift.diamond_count,
|
||||||
|
name: gift.name,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
return returnGifts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -80,38 +104,95 @@ export class GameController {
|
||||||
messageId: data.msgId,
|
messageId: data.msgId,
|
||||||
content: data.comment,
|
content: data.comment,
|
||||||
});
|
});
|
||||||
|
this.chatHandlerForGameInteraction(message, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the game input from the chat event for game interaction
|
||||||
|
* @param message {Message}
|
||||||
|
* @param user {User}
|
||||||
|
*/
|
||||||
|
private chatHandlerForGameInteraction(message: Message, user: User): void {
|
||||||
let gameKeyInterpretation = message.getMessageGameKey();
|
let gameKeyInterpretation = message.getMessageGameKey();
|
||||||
if (gameKeyInterpretation !== false) {
|
if (gameKeyInterpretation !== false) {
|
||||||
this.mgbaHttp
|
this.getUserStorage().getUser(user.getUserId()).then((storageUser) => {
|
||||||
.buttonTapCreate({
|
console.log(storageUser);
|
||||||
key: gameKeyInterpretation.toString(),
|
if (storageUser === undefined) {
|
||||||
})
|
this.getUserStorage().createUser(parseInt(user.getUserId()), FREE_GAME_INPUT_MOVES);
|
||||||
.then(() => {
|
this.chatHandlerForGameInteraction(message, user);
|
||||||
console.log(
|
return;
|
||||||
`${user.getUsername} sent button tap for ${gameKeyInterpretation}`
|
}
|
||||||
);
|
|
||||||
});
|
let hasBalance = storageUser.credits > 0 || this.getDeveloperUserIds().includes(parseInt(user.getUserId()));
|
||||||
|
if(hasBalance) {
|
||||||
|
// Subtract the credit from the user when the user is not a developer
|
||||||
|
if(!this.getDeveloperUserIds().includes(parseInt(user.getUserId()))) {
|
||||||
|
this.getUserStorage().updateUserCredits(user.getUserId(), storageUser.credits - 1);
|
||||||
|
}
|
||||||
|
// Send the button tap to the mGBA API
|
||||||
|
this.mgbaHttpClient
|
||||||
|
.buttonTapCreate({
|
||||||
|
key: gameKeyInterpretation.toString(),
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
Logger.logMessage(`Button tap for ${gameKeyInterpretation} created by ${user.getDisplayName()}`, "GameInput");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user IDs for the developers
|
||||||
|
* @returns {number[]} The user IDs for the developers
|
||||||
|
*/
|
||||||
|
private getDeveloperUserIds(): number[] {
|
||||||
|
return [
|
||||||
|
234457066013368320 // Dennis
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the gift event from the TikTok Live connection
|
* Handle the gift event from the TikTok Live connection
|
||||||
* @param {any} data The data object for the gift event
|
* @param {any} data The data object for the gift event
|
||||||
*/
|
*/
|
||||||
private giftHandler(data: any): void {
|
private giftHandler(data: any): void {
|
||||||
|
let giftObject = this.availableGifts.get(data.giftId);
|
||||||
|
if(giftObject) {
|
||||||
|
let giftValue = giftObject.getCostDiamonds();
|
||||||
|
this.getUserStorage().getUser(data.userId).then((storageUser) => {
|
||||||
|
if(storageUser === undefined) {
|
||||||
|
this.getUserStorage().createUser(parseInt(data.userId), giftValue + FREE_GAME_INPUT_MOVES);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.getUserStorage().updateUserCredits(data.userId, storageUser.credits + giftValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): void {
|
/**
|
||||||
|
* Get the database storage object for the users
|
||||||
|
* @returns {UserStorage} The UserStorage object for the GameController
|
||||||
|
*/
|
||||||
|
private getUserStorage(): UserStorage {
|
||||||
|
return this.userStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the GameController and connect to the TikTok Live
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
private startGameController(): void {
|
||||||
|
Logger.logMessage(`Trying to connect to the TikTok Livestram: ${this.channelName}`, "GameController", LogLevel.INFO);
|
||||||
this.getWebcastPushConnection()
|
this.getWebcastPushConnection()
|
||||||
.connect()
|
.connect()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Connected to TikTok Live");
|
Logger.logMessage("Connection successfully established", "WebcastConnection", LogLevel.INFO);
|
||||||
this.webcastPushConnection.on("chat", this.chatHandler.bind(this));
|
this.webcastPushConnection.on("chat", this.chatHandler.bind(this));
|
||||||
this.webcastPushConnection.on("gift", this.giftHandler.bind(this));
|
this.webcastPushConnection.on("gift", this.giftHandler.bind(this));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Failed to connect", error);
|
Logger.logDebug(`The connection could not be established`, error, "WebcastConnection", LogLevel.ERROR);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
76
node/source/classes/modules/database/Storage.ts
Normal file
76
node/source/classes/modules/database/Storage.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { Database } from 'sqlite3';
|
||||||
|
import { IUser } from './interfaces/IUser';
|
||||||
|
import { Logger, LogLevel } from '../logging/Logging';
|
||||||
|
|
||||||
|
export class UserStorage {
|
||||||
|
private database: Database;
|
||||||
|
constructor() {
|
||||||
|
Logger.logMessage("Establishing connection to UserStorage database", "Database", LogLevel.INFO);
|
||||||
|
this.database = new Database('../storage/UserStorage.sqlite3');
|
||||||
|
this.runMigrations();
|
||||||
|
Logger.logMessage("Connection established", "Database", LogLevel.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the database migrations
|
||||||
|
*/
|
||||||
|
private runMigrations(): void {
|
||||||
|
this.database.serialize(() => {
|
||||||
|
Logger.logMessage("Run database migration", "Database", LogLevel.INFO);
|
||||||
|
this.database.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, credits INTEGER)');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the credits for a user in the database
|
||||||
|
* @param userId {string}
|
||||||
|
* @param credits {number}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async updateUserCredits(userId: string, credits: number): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.database.run('UPDATE users SET credits = ? WHERE id = ?', [credits, userId], (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user from the database, if the user does not exist, it will return undefined
|
||||||
|
* @param userId {string}
|
||||||
|
* @returns {Promise<IUser>}
|
||||||
|
*/
|
||||||
|
public async getUser(userId: string): Promise<IUser> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.database.get('SELECT * FROM users WHERE id = ?', [userId], (err, row: IUser) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user in the database
|
||||||
|
* @param userId {number}
|
||||||
|
* @param credits {number}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async createUser(userId: number, credits: number): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.database.run('INSERT INTO users (id, credits) VALUES (?, ?)', [userId, credits], (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
4
node/source/classes/modules/database/interfaces/IUser.ts
Normal file
4
node/source/classes/modules/database/interfaces/IUser.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IUser {
|
||||||
|
id: number;
|
||||||
|
credits: number;
|
||||||
|
}
|
||||||
51
node/source/classes/modules/logging/Logging.ts
Normal file
51
node/source/classes/modules/logging/Logging.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
export enum LogLevel {
|
||||||
|
INFO = 1,
|
||||||
|
WARNING = 2,
|
||||||
|
ERROR = 3,
|
||||||
|
DEBUG = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
/**
|
||||||
|
* Log a message to the console
|
||||||
|
* @param message {string}
|
||||||
|
* @param category {string}
|
||||||
|
* @param level {LogLevel}
|
||||||
|
*/
|
||||||
|
static logMessage(message: string, category: string = "App", level: LogLevel = LogLevel.INFO) {
|
||||||
|
console.log(`[${this.logLevelToString(level)}] [${category}] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message to the console with additional data
|
||||||
|
* @param message {string}
|
||||||
|
* @param data {any}
|
||||||
|
* @param category {string}
|
||||||
|
* @param level {LogLevel}
|
||||||
|
*/
|
||||||
|
static logDebug(message: string, data: any, category: string = "App", level: LogLevel = LogLevel.INFO) {
|
||||||
|
this.logMessage(message, category, level);
|
||||||
|
console.log(data);
|
||||||
|
console.log("--------------------------------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string representation of a log level
|
||||||
|
* @param level {LogLevel} The log level to convert to a string
|
||||||
|
* @returns The string representation of the log level
|
||||||
|
*/
|
||||||
|
static logLevelToString(level: LogLevel): string {
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel.INFO:
|
||||||
|
return "INFO";
|
||||||
|
case LogLevel.WARNING:
|
||||||
|
return "WARNING";
|
||||||
|
case LogLevel.ERROR:
|
||||||
|
return "ERROR";
|
||||||
|
case LogLevel.DEBUG:
|
||||||
|
return "DEBUG";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { GameController } from "./classes/controller/GameController";
|
import { GameController } from "./classes/controller/GameController";
|
||||||
|
import { UserStorage } from "./classes/modules/database/Storage";
|
||||||
|
|
||||||
let gameController = new GameController("niknando");
|
new GameController("mr.vadim_");
|
||||||
gameController.start();
|
|
||||||
|
new UserStorage();
|
||||||
0
storage/.gitkeep
Normal file
0
storage/.gitkeep
Normal file
Loading…
Reference in a new issue