implement url badges and some mp badges
This commit is contained in:
parent
f72bdd3f27
commit
9dfc7e4100
10 changed files with 223 additions and 38 deletions
|
@ -1,7 +1,7 @@
|
|||
// ==UserScript==
|
||||
// @name MultiProbe
|
||||
// @namespace https://*.angusnicneven.com/*
|
||||
// @version 20241002.4
|
||||
// @version 20241003.0
|
||||
// @description Probe with friends!
|
||||
// @author tgpholly
|
||||
// @match https://*.angusnicneven.com/*
|
||||
|
@ -55,7 +55,7 @@ console.log("[MP] MultiProbe init");
|
|||
'use strict';
|
||||
|
||||
// Make sure to change the userscript version too!!!!!!!!!!
|
||||
const USERSCRIPT_VERSION_RAW = "20241002.4";
|
||||
const USERSCRIPT_VERSION_RAW = "20241003.0";
|
||||
const USERSCRIPT_VERSION = parseInt(USERSCRIPT_VERSION_RAW.replace(".", ""));
|
||||
|
||||
if (!continueRunningScript) {
|
||||
|
@ -889,9 +889,9 @@ mp_button {
|
|||
}
|
||||
case MessageType.BadgeUnlock:
|
||||
{
|
||||
const badgeTitle = reader.readShortString();
|
||||
const badgeDescription = reader.readString();
|
||||
const badgeIconUrl = reader.readShortString();
|
||||
const badgeTitle = reader.readString16();
|
||||
const badgeDescription = reader.readString16();
|
||||
const badgeIconUrl = reader.readString16();
|
||||
|
||||
createBadgePopup(badgeTitle, badgeDescription, badgeIconUrl);
|
||||
}
|
||||
|
@ -1095,7 +1095,7 @@ mp_button {
|
|||
popup.appendChild(badgeTitle);
|
||||
|
||||
const badgeDescription = document.createElement("mp_text");
|
||||
badgeDescription.innerText = description;
|
||||
badgeDescription.innerHTML = description;
|
||||
badgeDescription.style = "position:absolute;top:1.1rem;left:calc(1rem + 32px)";
|
||||
popup.appendChild(badgeDescription);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ export default class User {
|
|||
public PasswordSalt:string;
|
||||
public PasswordHash:string;
|
||||
public APIKey:string;
|
||||
public HasUsedClient:boolean;
|
||||
public CreatedByUserId:number;
|
||||
public CreatedDatetime:Date;
|
||||
public LastModifiedByUserId?:number;
|
||||
|
@ -18,31 +19,16 @@ export default class User {
|
|||
public DeletedDatetime?:Date;
|
||||
public IsDeleted:boolean;
|
||||
|
||||
public constructor(id?:number, userLevel?:UserLevel, username?:string, passwordSalt?:string, passwordHash?:string, apiKey?:string, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) {
|
||||
if (typeof(id) == "number" && typeof(userLevel) == "number" && typeof(username) == "string" && typeof(passwordHash) == "string" && typeof(passwordSalt) == "string" && typeof(apiKey) == "string" && typeof(createdByUserId) == "number" && createdDateTime instanceof Date && typeof(lastModifiedByUserId) == "number" && lastModifiedDatetime instanceof Date && typeof(deletedByUserId) == "number" && deletedDatetime instanceof Date && typeof(isDeleted) == "boolean") {
|
||||
this.Id = id;
|
||||
this.UserLevel = userLevel;
|
||||
this.Username = username;
|
||||
this.PasswordHash = passwordHash;
|
||||
this.PasswordSalt = passwordSalt;
|
||||
this.APIKey = apiKey;
|
||||
this.CreatedByUserId = createdByUserId;
|
||||
this.CreatedDatetime = createdDateTime;
|
||||
this.LastModifiedByUserId = lastModifiedByUserId;
|
||||
this.LastModifiedDatetime = lastModifiedDatetime;
|
||||
this.DeletedByUserId = deletedByUserId;
|
||||
this.DeletedDatetime = deletedDatetime;
|
||||
this.IsDeleted = isDeleted;
|
||||
} else {
|
||||
public constructor() {
|
||||
this.Id = Number.MIN_VALUE;
|
||||
this.UserLevel = UserLevel.Unknown;
|
||||
this.Username = "";
|
||||
this.PasswordHash = "";
|
||||
this.PasswordSalt = "";
|
||||
this.APIKey = "";
|
||||
this.HasUsedClient = false;
|
||||
this.CreatedByUserId = Number.MIN_VALUE;
|
||||
this.CreatedDatetime = new Date(0);
|
||||
this.IsDeleted = false;
|
||||
}
|
||||
}
|
||||
}
|
21
server/entities/UserBadge.ts
Normal file
21
server/entities/UserBadge.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
export default class UserBadge {
|
||||
public Id: number;
|
||||
public UserId: number;
|
||||
public BadgeId: number;
|
||||
public CreatedByUserId: number;
|
||||
public CreatedDatetime: Date;
|
||||
public LastModifiedByUserId?: number;
|
||||
public LastModifiedDatetime?: Date;
|
||||
public DeletedByUserId?: number;
|
||||
public DeletedDatetime?: Date;
|
||||
public IsDeleted: boolean;
|
||||
|
||||
public constructor() {
|
||||
this.Id = Number.MIN_VALUE;
|
||||
this.UserId = Number.MIN_VALUE;
|
||||
this.BadgeId = Number.MIN_VALUE;
|
||||
this.CreatedByUserId = Number.MIN_VALUE;
|
||||
this.CreatedDatetime = new Date(0);
|
||||
this.IsDeleted = false;
|
||||
}
|
||||
}
|
|
@ -50,6 +50,8 @@ import PartyService from "./services/PartyService";
|
|||
import AdminController_Auth$Admin from "./controller/AdminController";
|
||||
import { join } from "path";
|
||||
import WsData from "./objects/WsData";
|
||||
import BadgeCache from "./objects/BadgeCache";
|
||||
import Badge from "./entities/Badge";
|
||||
|
||||
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
||||
|
||||
|
@ -58,6 +60,8 @@ WsData.users = users;
|
|||
|
||||
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name);
|
||||
|
||||
BadgeCache.RefreshCache();
|
||||
|
||||
// Web stuff
|
||||
|
||||
const fastify = Fastify({
|
||||
|
@ -237,9 +241,10 @@ websocketServer.on("connection", (socket) => {
|
|||
}
|
||||
|
||||
let page = rawURL.toLowerCase().replace(".htm", "").replace(".html", "");
|
||||
if (page === "index") {
|
||||
page = "";
|
||||
if (page.endsWith("/index")) {
|
||||
page = page.replace("/index", "/");
|
||||
}
|
||||
|
||||
let lengthOfUsernames = 0;
|
||||
const usersOnPage = new Array<RemoteUser>();
|
||||
await users.forEach(otherUser => {
|
||||
|
@ -261,6 +266,40 @@ websocketServer.on("connection", (socket) => {
|
|||
user.send(usersToSend.toBuffer());
|
||||
sendGroupUpdate(user);
|
||||
updateConnectionMetrics();
|
||||
|
||||
const badgeForPage = BadgeCache.UrlBadges.get(page);
|
||||
if (badgeForPage && await UserService.UnlockBadgeIfNotUnlocked(dbUser.Id, badgeForPage.Id)) {
|
||||
user.sendBadge(badgeForPage.Name, badgeForPage.Description, badgeForPage.ImageUrl);
|
||||
}
|
||||
|
||||
const goalBadgeKeys = BadgeCache.GoalBadges.keys;
|
||||
let badge:Badge;
|
||||
for (const key of goalBadgeKeys) {
|
||||
switch (key) {
|
||||
case "use_first_time":
|
||||
{
|
||||
if (!dbUser.HasUsedClient) {
|
||||
await UserService.SaveUserClientLoginFlag(dbUser.Id);
|
||||
dbUser.HasUsedClient = true;
|
||||
badge = BadgeCache.GoalBadges.get(key)!;
|
||||
if (await UserService.UnlockBadgeIfNotUnlocked(dbUser.Id, badge.Id)) {
|
||||
user.sendBadge(badge.Name, badge.Description, badge.ImageUrl);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "first_time_party":
|
||||
{
|
||||
if (dbUserParty) {
|
||||
badge = BadgeCache.GoalBadges.get(key)!;
|
||||
if (await UserService.UnlockBadgeIfNotUnlocked(dbUser.Id, badge.Id)) {
|
||||
user.sendBadge(badge.Name, badge.Description, badge.ImageUrl);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageType.CursorPos:
|
||||
|
|
30
server/objects/BadgeCache.ts
Normal file
30
server/objects/BadgeCache.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Badge from "../entities/Badge";
|
||||
import FunkyArray from "funky-array";
|
||||
import BadgeService from "../services/BadgeService";
|
||||
|
||||
export default abstract class BadgeCache {
|
||||
public static UrlBadges:FunkyArray<string, Badge>;
|
||||
public static GoalBadges:FunkyArray<string, Badge>;
|
||||
|
||||
public static async RefreshCache() {
|
||||
const badges = await BadgeService.LoadAll();
|
||||
const urlBadges = new FunkyArray<string, Badge>();
|
||||
const goalBadges = new FunkyArray<string, Badge>();
|
||||
for (const badge of badges) {
|
||||
const urlParts = badge.ForUrl.split("://");
|
||||
let url = urlParts[1].replace("www.", "").toLowerCase().replace(".htm", "").replace(".html", "");
|
||||
if (url.endsWith("/index")) {
|
||||
url = url.replace("/index", "/");
|
||||
}
|
||||
|
||||
if (urlParts[0] === "mp") {
|
||||
goalBadges.set(url, badge);
|
||||
} else {
|
||||
urlBadges.set(url, badge);
|
||||
}
|
||||
}
|
||||
|
||||
BadgeCache.UrlBadges = urlBadges;
|
||||
BadgeCache.GoalBadges = goalBadges;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import { createWriter, Endian } from "bufferstuff";
|
||||
import IMetric from "simple-prom/lib/interfaces/IMetric";
|
||||
import { WebSocket } from "ws";
|
||||
import { MessageType } from "../enums/MessageType";
|
||||
|
||||
export default class RemoteUser {
|
||||
private static USER_IDS = 0;
|
||||
|
@ -47,4 +49,9 @@ export default class RemoteUser {
|
|||
this.messagesOut.add(1);
|
||||
this.socket.send(data);
|
||||
}
|
||||
|
||||
sendBadge(name:string, description:string, imageUrl:string) {
|
||||
const formattedDescription = description.replaceAll("\r", "").replaceAll("\n", "<br>");
|
||||
this.send(createWriter(Endian.LE, 7 + name.length * 2 + formattedDescription.length * 2 + imageUrl.length * 2).writeByte(MessageType.BadgeUnlock).writeString16(name).writeString16(formattedDescription).writeString16(imageUrl).toBuffer());
|
||||
}
|
||||
}
|
54
server/repos/UserBadgeRepo.ts
Normal file
54
server/repos/UserBadgeRepo.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import UserBadge from "../entities/UserBadge";
|
||||
import Database from "../objects/Database";
|
||||
import RepoBase from "./RepoBase";
|
||||
|
||||
export default abstract class UserBadgeRepo {
|
||||
public static async selectById(id:number) {
|
||||
const dbUserBadge = await Database.Instance.query("SELECT * FROM UserBadge WHERE Id = ? AND IsDeleted = 0 LIMIT 1", [id]);
|
||||
if (dbUserBadge == null || dbUserBadge.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
const userBadge = new UserBadge();
|
||||
populateUserBadgeFromDB(userBadge, dbUserBadge[0]);
|
||||
return userBadge;
|
||||
}
|
||||
}
|
||||
|
||||
public static async selectByUserIdBadgeId(userId:number, badgeId:number) {
|
||||
const dbUserBadge = await Database.Instance.query("SELECT * FROM UserBadge WHERE UserId = ? AND BadgeId = ? AND IsDeleted = 0 LIMIT 1", [ userId, badgeId ]);
|
||||
if (dbUserBadge == null || dbUserBadge.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
const userBadge = new UserBadge();
|
||||
populateUserBadgeFromDB(userBadge, dbUserBadge[0]);
|
||||
return userBadge;
|
||||
}
|
||||
}
|
||||
|
||||
public static async insertUpdate(userBadge:UserBadge) {
|
||||
if (userBadge.Id === Number.MIN_VALUE) {
|
||||
userBadge.Id = (await Database.Instance.query("INSERT UserBadge (UserId, BadgeId, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||
userBadge.UserId, userBadge.BadgeId, userBadge.CreatedByUserId, userBadge.CreatedDatetime.getTime(), userBadge.LastModifiedByUserId ?? null, userBadge.LastModifiedDatetime?.getTime() ?? null, userBadge.DeletedByUserId ?? null, userBadge.DeletedDatetime?.getTime() ?? null, Number(userBadge.IsDeleted)
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE UserBadge SET UserId = ?, BadgeId = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||
userBadge.UserId, userBadge.BadgeId, userBadge.CreatedByUserId, userBadge.CreatedDatetime.getTime(), userBadge.LastModifiedByUserId ?? null, userBadge.LastModifiedDatetime?.getTime() ?? null, userBadge.DeletedByUserId ?? null, userBadge.DeletedDatetime?.getTime() ?? null, Number(userBadge.IsDeleted), userBadge.Id
|
||||
]);
|
||||
}
|
||||
|
||||
return userBadge;
|
||||
}
|
||||
}
|
||||
|
||||
function populateUserBadgeFromDB(userBadge:UserBadge, dbUser:any) {
|
||||
userBadge.Id = dbUser.Id;
|
||||
userBadge.UserId = dbUser.UserId;
|
||||
userBadge.BadgeId = dbUser.BadgeId;
|
||||
userBadge.CreatedByUserId = dbUser.CreatedByUserId;
|
||||
userBadge.CreatedDatetime = new Date(dbUser.CreatedDatetime);
|
||||
userBadge.LastModifiedByUserId = dbUser.LastModifiedByUserId;
|
||||
userBadge.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.LastModifiedDatetime);
|
||||
userBadge.DeletedByUserId = dbUser.DeletedByUserId;
|
||||
userBadge.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.DeletedDatetime);
|
||||
userBadge.IsDeleted = dbUser.IsDeleted === 1;
|
||||
}
|
|
@ -57,12 +57,12 @@ export default class UserRepo {
|
|||
|
||||
public static async insertUpdate(user:User) {
|
||||
if (user.Id === Number.MIN_VALUE) {
|
||||
user.Id = (await Database.Instance.query("INSERT User (UserLevelId, Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||
user.Id = (await Database.Instance.query("INSERT User (UserLevelId, Username, PasswordHash, PasswordSalt, HasUsedClient, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE User SET UserLevelId = ?, Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||
await Database.Instance.query(`UPDATE User SET UserLevelId = ?, Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, HasUsedClient = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, Number(user.HasUsedClient), user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,7 @@ function populateUserFromDB(user:User, dbUser:any) {
|
|||
user.PasswordHash = dbUser.PasswordHash;
|
||||
user.PasswordSalt = dbUser.PasswordSalt;
|
||||
user.APIKey = dbUser.APIKey;
|
||||
user.HasUsedClient = dbUser.HasUsedClient === 1;
|
||||
user.CreatedByUserId = dbUser.CreatedByUserId;
|
||||
user.CreatedDatetime = new Date(dbUser.CreatedDatetime);
|
||||
user.LastModifiedByUserId = dbUser.LastModifiedByUserId;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Console } from "hsconsole";
|
||||
import Badge from "../entities/Badge";
|
||||
import BadgeRepo from "../repos/BadgeRepo";
|
||||
import BadgeCache from "../objects/BadgeCache";
|
||||
|
||||
export default abstract class BadgeService {
|
||||
public static async SaveBadge(currentUserId:number, id:number | undefined, name:string, description:string, imageUrl: string, forUrl:string) {
|
||||
|
@ -20,7 +21,11 @@ export default abstract class BadgeService {
|
|||
badge.ImageUrl = imageUrl;
|
||||
badge.ForUrl = forUrl;
|
||||
|
||||
return await BadgeRepo.insertUpdate(badge);
|
||||
badge = await BadgeRepo.insertUpdate(badge);
|
||||
|
||||
await BadgeCache.RefreshCache();
|
||||
|
||||
return badge;
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
throw e;
|
||||
|
|
|
@ -5,6 +5,8 @@ import PasswordUtility from "../utilities/PasswordUtility";
|
|||
import UserParty from "../entities/UserParty";
|
||||
import UserPartyRepo from "../repos/UserPartyRepo";
|
||||
import { UserLevel } from "../enums/UserLevel";
|
||||
import UserBadgeRepo from "../repos/UserBadgeRepo";
|
||||
import UserBadge from "../entities/UserBadge";
|
||||
|
||||
export default class UserService {
|
||||
public static async AuthenticateUser(username:string, password:string) {
|
||||
|
@ -227,4 +229,44 @@ export default class UserService {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static async UnlockBadgeIfNotUnlocked(currentUserId:number, badgeId:number) {
|
||||
try {
|
||||
let userBadge = await UserBadgeRepo.selectByUserIdBadgeId(currentUserId, badgeId);
|
||||
if (userBadge) {
|
||||
return false;
|
||||
}
|
||||
|
||||
userBadge = new UserBadge();
|
||||
userBadge.UserId = currentUserId;
|
||||
userBadge.BadgeId = badgeId;
|
||||
userBadge.CreatedByUserId = currentUserId;
|
||||
userBadge.CreatedDatetime = new Date();
|
||||
|
||||
await UserBadgeRepo.insertUpdate(userBadge)
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static async SaveUserClientLoginFlag(currentUserId:number) {
|
||||
try {
|
||||
const user = await UserRepo.selectById(currentUserId);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
user.HasUsedClient = true;
|
||||
user.LastModifiedByUserId = currentUserId;
|
||||
user.LastModifiedDatetime = new Date();
|
||||
|
||||
return await UserRepo.insertUpdate(user);
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue