Der Prototyp mit Datenbankanbindung für das Speichern von Geschenkwerten (für die freien Züge)

This commit is contained in:
Dennis Heinrich 2024-05-10 19:30:03 +02:00
parent f14ac03059
commit 8a65ce78a5
20 changed files with 1243 additions and 41 deletions

3
.gitignore vendored
View file

@ -6,5 +6,4 @@
/node/dist /node/dist
# Ignore everything except save files # Ignore everything except save files
/roms/* /storage/*.sqlite3
!/roms/*.sav

View file

@ -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"
}, },

File diff suppressed because it is too large Load diff

View file

@ -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.mgbaHttpClient = new MgbaHttp({
baseUrl: "http://localhost:5000",
});
// Load the available gifts from the TikTok platform and start the GameController
this.getAvailableGifts().then((gifts) => {
this.availableGifts = gifts;
this.startGameController();
});
}
this.getWebcastPushConnection().getAvailableGifts().then((gifts) => { /**
* 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) => { gifts.forEach((gift: any) => {
this.availableGifts.set(gift.id, new Gift({ returnGifts.set(gift.id, new Gift({
giftId: gift.id, giftId: gift.id,
costDiamonds: gift.diamond_count, costDiamonds: gift.diamond_count,
name: gift.name, name: gift.name,
})); }));
}); });
}); return returnGifts;
} }
/** /**
@ -80,18 +104,52 @@ 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) => {
console.log(storageUser);
if (storageUser === undefined) {
this.getUserStorage().createUser(parseInt(user.getUserId()), FREE_GAME_INPUT_MOVES);
this.chatHandlerForGameInteraction(message, user);
return;
}
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({ .buttonTapCreate({
key: gameKeyInterpretation.toString(), key: gameKeyInterpretation.toString(),
}) })
.then(() => { .then(() => {
console.log( Logger.logMessage(`Button tap for ${gameKeyInterpretation} created by ${user.getDisplayName()}`, "GameInput");
`${user.getUsername} sent button tap for ${gameKeyInterpretation}`
);
}); });
} }
});
}
}
/**
* Get the user IDs for the developers
* @returns {number[]} The user IDs for the developers
*/
private getDeveloperUserIds(): number[] {
return [
234457066013368320 // Dennis
]
} }
/** /**
@ -99,19 +157,42 @@ export class GameController {
* @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);
}); });
} }
} }

View 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();
}
});
});
}
}

View file

@ -0,0 +1,4 @@
export interface IUser {
id: number;
credits: number;
}

View 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";
}
}
}

View file

@ -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
View file