mirror of
https://github.com/icewind1991/mx-puppet-steam.git
synced 2026-06-03 17:44:09 +02:00
implement basic group chats
This commit is contained in:
parent
8d2318e33f
commit
6dd7bf525e
4 changed files with 140 additions and 29 deletions
|
|
@ -5,14 +5,16 @@ Matrix <-> Steam puppeting bridge based on [mx-puppet-bridge](https://github.com
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
- [x] login with steam guard support
|
- [x] login with steam guard support
|
||||||
- [x] 1 <->1 messaging
|
- [x] 1<->1 messaging
|
||||||
- [ ] group messaging
|
- [x] group messaging
|
||||||
- [x] steam -> matrix typing notifications
|
- [x] steam -> matrix typing notifications
|
||||||
- [x] online/offline status
|
- [x] online/offline status
|
||||||
- [x] retrieve nickname and avatar from steam
|
- [x] retrieve nickname and avatar from steam
|
||||||
- [x] listing of steam users
|
- [x] listing of steam users
|
||||||
- [ ] listing of steam group chats
|
- [ ] listing of steam group chats
|
||||||
- [x] bridging embedded images
|
- [x] bridging embedded images in 1<->1 chats
|
||||||
|
- [x] receiving embedded images from steam in group chats
|
||||||
|
- [ ] sending embedded images to steam in group chats
|
||||||
|
|
||||||
## Linking
|
## Linking
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,10 @@ async function run() {
|
||||||
puppet.on("message", steam.handleMatrixMessage.bind(steam));
|
puppet.on("message", steam.handleMatrixMessage.bind(steam));
|
||||||
puppet.on("image", steam.handleMatrixImage.bind(steam));
|
puppet.on("image", steam.handleMatrixImage.bind(steam));
|
||||||
puppet.setCreateUserHook(steam.createUser.bind(steam));
|
puppet.setCreateUserHook(steam.createUser.bind(steam));
|
||||||
// puppet.setGetUserIdsInRoomHook(steam.getUserIdsInRoom.bind(steam));
|
|
||||||
puppet.setListUsersHook(steam.listUsers.bind(steam));
|
puppet.setListUsersHook(steam.listUsers.bind(steam));
|
||||||
puppet.setGetDmRoomIdHook(steam.getDmRoomId.bind(steam));
|
puppet.setGetDmRoomIdHook(steam.getDmRoomId.bind(steam));
|
||||||
puppet.setCreateRoomHook(steam.createRoom.bind(steam));
|
puppet.setCreateRoomHook(steam.createRoom.bind(steam));
|
||||||
|
puppet.setCreateGroupHook(steam.createGroup.bind(steam));
|
||||||
puppet.setGetDescHook(async (puppetId: number, data: any): Promise<string> => {
|
puppet.setGetDescHook(async (puppetId: number, data: any): Promise<string> => {
|
||||||
let s = "Steam";
|
let s = "Steam";
|
||||||
if (data.screenName) {
|
if (data.screenName) {
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,31 @@ export interface IIncomingFriendMessage {
|
||||||
ordinal: number,
|
ordinal: number,
|
||||||
local_echo: boolean,
|
local_echo: boolean,
|
||||||
low_priority: boolean
|
low_priority: boolean
|
||||||
message_bbcode_parsed: BBCodeNode[]
|
message_bbcode_parsed: BBCodeField[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BBCodeField = BBCodeNode | string;
|
||||||
|
|
||||||
|
export function isBBCode(field: BBCodeField): field is BBCodeNode {
|
||||||
|
return field['tag'] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BBCodeNode {
|
export interface BBCodeNode {
|
||||||
tag: string
|
tag: string
|
||||||
attrs: { [attr: string]: string },
|
attrs: { [attr: string]: string },
|
||||||
content: BBCodeNode[]
|
content: BBCodeField[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIncomingChatMessage {
|
export interface IIncomingChatMessage {
|
||||||
chat_group_id: string,
|
chat_group_id: string,
|
||||||
chat_id: string,
|
chat_id: string,
|
||||||
|
chat_name: string,
|
||||||
steamid_sender: SteamId,
|
steamid_sender: SteamId,
|
||||||
chat_entry_type: EChatEntryType,
|
chat_entry_type?: EChatEntryType,
|
||||||
from_limited_account: boolean,
|
from_limited_account?: boolean,
|
||||||
message: string,
|
message: string,
|
||||||
message_no_bbcode: string,
|
message_no_bbcode: string,
|
||||||
|
message_bbcode_parsed: BBCodeField[]
|
||||||
server_timestamp: Date,
|
server_timestamp: Date,
|
||||||
ordinal: number,
|
ordinal: number,
|
||||||
mentions: IChatMentions | null,
|
mentions: IChatMentions | null,
|
||||||
|
|
@ -82,3 +90,12 @@ export interface AppInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGroupInfo {
|
||||||
|
chat_group_id: string,
|
||||||
|
default_chat_id: string,
|
||||||
|
chat_rooms: {
|
||||||
|
chat_id: string,
|
||||||
|
chat_name: string,
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
|
||||||
134
src/steam.ts
134
src/steam.ts
|
|
@ -13,8 +13,9 @@ import * as SteamCommunity from "steamcommunity";
|
||||||
import * as SteamID from "steamid";
|
import * as SteamID from "steamid";
|
||||||
import {EPersonaState} from "./enum";
|
import {EPersonaState} from "./enum";
|
||||||
import {MatrixPresence} from "mx-puppet-bridge/lib/src/presencehandler";
|
import {MatrixPresence} from "mx-puppet-bridge/lib/src/presencehandler";
|
||||||
import {AppInfo, IIncomingFriendMessage, IPersona} from "./interfaces";
|
import {AppInfo, IGroupInfo, IIncomingChatMessage, IIncomingFriendMessage, IPersona, isBBCode} from "./interfaces";
|
||||||
import {IRetList} from "mx-puppet-bridge/src/interfaces";
|
import {IRetList} from "mx-puppet-bridge/src/interfaces";
|
||||||
|
import {IRemoteGroup} from "mx-puppet-bridge/lib/src";
|
||||||
|
|
||||||
const log = new Log("MatrixPuppet:Steam");
|
const log = new Log("MatrixPuppet:Steam");
|
||||||
|
|
||||||
|
|
@ -25,7 +26,13 @@ interface ISteamPuppet {
|
||||||
sentEventIds: string[];
|
sentEventIds: string[];
|
||||||
knownPersonas: Map<string, IPersona>,
|
knownPersonas: Map<string, IPersona>,
|
||||||
knownApps: Map<string, AppInfo>,
|
knownApps: Map<string, AppInfo>,
|
||||||
ourSendImages: string[]
|
ourSendImages: string[],
|
||||||
|
knownChats: Map<string, {
|
||||||
|
chat_group_id: string,
|
||||||
|
chat_id: string,
|
||||||
|
chat_name: string
|
||||||
|
}>,
|
||||||
|
knownGroupNames: Map<string, string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISteamPuppets {
|
interface ISteamPuppets {
|
||||||
|
|
@ -75,7 +82,7 @@ export class Steam {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSendParams(puppetId: number, msg: IIncomingFriendMessage, fromSteamId?: SteamID): Promise<IReceiveParams> {
|
public async getFriendMessageSendParams(puppetId: number, msg: IIncomingFriendMessage, fromSteamId?: SteamID): Promise<IReceiveParams> {
|
||||||
const p = this.puppets[puppetId];
|
const p = this.puppets[puppetId];
|
||||||
|
|
||||||
let persona = await this.getPersona(p, fromSteamId ? fromSteamId : msg.steamid_friend);
|
let persona = await this.getPersona(p, fromSteamId ? fromSteamId : msg.steamid_friend);
|
||||||
|
|
@ -96,6 +103,37 @@ export class Steam {
|
||||||
} as IReceiveParams;
|
} as IReceiveParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getChatMessageSendParams(puppetId: number, msg: IIncomingChatMessage, fromSteamId?: SteamID): Promise<IReceiveParams> {
|
||||||
|
const p = this.puppets[puppetId];
|
||||||
|
|
||||||
|
let persona = await this.getPersona(p, fromSteamId ? fromSteamId : msg.steamid_sender);
|
||||||
|
|
||||||
|
return {
|
||||||
|
room: {
|
||||||
|
puppetId,
|
||||||
|
roomId: `chat_${msg.chat_group_id}_${msg.chat_id}`,
|
||||||
|
isDirect: false,
|
||||||
|
name: msg.chat_name,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
puppetId,
|
||||||
|
userId: fromSteamId ? fromSteamId.toString() : msg.steamid_sender.toString(),
|
||||||
|
name: persona.player_name,
|
||||||
|
avatarUrl: persona.avatar_url_medium
|
||||||
|
},
|
||||||
|
eventId: `${msg.server_timestamp.toISOString()}::${msg.ordinal}`,
|
||||||
|
} as IReceiveParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseChatRoomId(roomId: string): [string, string] {
|
||||||
|
let matches = roomId.match(/chat_(\d+)_(\d+)/);
|
||||||
|
if (matches) {
|
||||||
|
return [matches[1], matches[2]];
|
||||||
|
} else {
|
||||||
|
throw new Error("invalid chatroom id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async newPuppet(puppetId: number, data: IPuppetParams) {
|
public async newPuppet(puppetId: number, data: IPuppetParams) {
|
||||||
log.info(`Adding new Puppet: puppetId=${puppetId}`);
|
log.info(`Adding new Puppet: puppetId=${puppetId}`);
|
||||||
if (this.puppets[puppetId]) {
|
if (this.puppets[puppetId]) {
|
||||||
|
|
@ -113,6 +151,8 @@ export class Steam {
|
||||||
knownPersonas: new Map(),
|
knownPersonas: new Map(),
|
||||||
knownApps: new Map(),
|
knownApps: new Map(),
|
||||||
ourSendImages: [],
|
ourSendImages: [],
|
||||||
|
knownChats: new Map(),
|
||||||
|
knownGroupNames: new Map(),
|
||||||
} as ISteamPuppet;
|
} as ISteamPuppet;
|
||||||
try {
|
try {
|
||||||
client.logOn({
|
client.logOn({
|
||||||
|
|
@ -189,6 +229,9 @@ export class Steam {
|
||||||
client.chat.on("friendTyping", (message: IIncomingFriendMessage) => {
|
client.chat.on("friendTyping", (message: IIncomingFriendMessage) => {
|
||||||
this.handleFriendTyping(puppetId, message);
|
this.handleFriendTyping(puppetId, message);
|
||||||
});
|
});
|
||||||
|
client.chat.on("chatMessage", (message) => {
|
||||||
|
this.handleChatMessage(puppetId, message);
|
||||||
|
});
|
||||||
|
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
log.error(`Failed to start up puppet ${puppetId}`, err);
|
log.error(`Failed to start up puppet ${puppetId}`, err);
|
||||||
|
|
@ -227,23 +270,51 @@ export class Steam {
|
||||||
|
|
||||||
public async handleFriendMessage(puppetId: number, message: IIncomingFriendMessage, fromSteamId?: SteamID) {
|
public async handleFriendMessage(puppetId: number, message: IIncomingFriendMessage, fromSteamId?: SteamID) {
|
||||||
const p = this.puppets[puppetId];
|
const p = this.puppets[puppetId];
|
||||||
log.verbose("Got message from steam to pass on");
|
log.verbose("Got friend message from steam to pass on");
|
||||||
|
|
||||||
let sendParams = await this.getSendParams(puppetId, message, fromSteamId);
|
let sendParams = await this.getFriendMessageSendParams(puppetId, message, fromSteamId);
|
||||||
|
|
||||||
|
await this.sendMessage(p, sendParams, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleChatMessage(puppetId: number, message: IIncomingChatMessage, fromSteamId?: SteamID) {
|
||||||
|
const p = this.puppets[puppetId];
|
||||||
|
log.verbose("Got chat message from steam to pass on");
|
||||||
|
|
||||||
|
if (!p.knownChats.has(message.chat_id)) {
|
||||||
|
p.knownChats.set(message.chat_id, {
|
||||||
|
chat_id: message.chat_id,
|
||||||
|
chat_group_id: message.chat_group_id,
|
||||||
|
chat_name: message.chat_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.knownGroupNames.has(message.chat_group_id)) {
|
||||||
|
let parts = message.chat_name.split('|');
|
||||||
|
p.knownGroupNames.set(message.chat_group_id, parts[0].trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendParams = await this.getChatMessageSendParams(puppetId, message, fromSteamId);
|
||||||
|
|
||||||
|
await this.sendMessage(p, sendParams, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendMessage(puppet: ISteamPuppet, sendParams: IReceiveParams, message: IIncomingFriendMessage | IIncomingChatMessage) {
|
||||||
// message is only an embedded image
|
// message is only an embedded image
|
||||||
if (
|
if (
|
||||||
message.message_bbcode_parsed.length === 1
|
message.message_bbcode_parsed
|
||||||
|
&& message.message_bbcode_parsed.length === 1
|
||||||
|
&& isBBCode(message.message_bbcode_parsed[0])
|
||||||
&& message.message_bbcode_parsed[0].tag === 'img'
|
&& message.message_bbcode_parsed[0].tag === 'img'
|
||||||
&& message.message_no_bbcode === message.message_bbcode_parsed[0].attrs['src']
|
&& message.message_no_bbcode === message.message_bbcode_parsed[0].attrs['src']
|
||||||
) {
|
) {
|
||||||
const url = message.message_bbcode_parsed[0].attrs['src'];
|
const url = message.message_bbcode_parsed[0].attrs['src'];
|
||||||
let i = p.ourSendImages.indexOf(url);
|
let i = puppet.ourSendImages.indexOf(url);
|
||||||
if (i === -1) {
|
if (i === -1) {
|
||||||
await this.bridge.sendImage(sendParams, url);
|
await this.bridge.sendImage(sendParams, url);
|
||||||
} else {
|
} else {
|
||||||
// image came from us, dont send
|
// image came from us, dont send
|
||||||
p.ourSendImages.splice(i);
|
puppet.ourSendImages.splice(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.bridge.sendMessage(sendParams, {
|
await this.bridge.sendMessage(sendParams, {
|
||||||
|
|
@ -279,7 +350,13 @@ export class Steam {
|
||||||
await this.bridge.eventSync.insert(room, eventId, id);
|
await this.bridge.eventSync.insert(room, eventId, id);
|
||||||
p.sentEventIds.push(id);
|
p.sentEventIds.push(id);
|
||||||
} else {
|
} else {
|
||||||
await this.bridge.sendStatusMessage(room.puppetId, `Sending group messages is currently not supported`);
|
let [groupId, chatId] = this.parseChatRoomId(room.roomId);
|
||||||
|
|
||||||
|
const sendMessage = await p.client.chat.sendChatMessage(groupId, chatId, msg);
|
||||||
|
let id = `${sendMessage.server_timestamp.toISOString()}::${sendMessage.ordinal}`;
|
||||||
|
|
||||||
|
await this.bridge.eventSync.insert(room, eventId, id);
|
||||||
|
p.sentEventIds.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,21 +446,36 @@ export class Steam {
|
||||||
puppetId: room.puppetId,
|
puppetId: room.puppetId,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
isDirect: true,
|
isDirect: true,
|
||||||
topic: persona.player_name
|
name: persona.player_name
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
await this.bridge.sendStatusMessage(room.puppetId, `Creating group room chats is currently not supported`);
|
let [groupId, chatId] = this.parseChatRoomId(room.roomId);
|
||||||
return null;
|
let chatRoom = p.knownChats.get(chatId);
|
||||||
|
if (chatRoom) {
|
||||||
|
return {
|
||||||
|
puppetId: room.puppetId,
|
||||||
|
roomId: `chat_${chatRoom.chat_group_id}_${chatRoom.chat_id}`,
|
||||||
|
isDirect: false,
|
||||||
|
groupId: chatRoom.chat_group_id,
|
||||||
|
name: chatRoom.chat_name,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public async getUserIdsInRoom(room: IRemoteRoom): Promise<Set<string> | null> {
|
await this.bridge.sendStatusMessage(room.puppetId, `Invalid room id or unknown chat: ${room.roomId}`);
|
||||||
// const p = this.puppets[room.puppetId];
|
return null;
|
||||||
// const client: TalkClient = p.client;
|
}
|
||||||
// const participants = await client.getChat(room.roomId).get_participants();
|
|
||||||
// for (const participant of participants) {
|
public async createGroup(room: IRemoteGroup): Promise<IRemoteGroup | null> {
|
||||||
// p.knownUserNames[participant.userId] = participant.displayName;
|
const p = this.puppets[room.puppetId];
|
||||||
// }
|
if (!p) {
|
||||||
// return new Set(participants.map((participant) => participant.userId));
|
return null;
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
puppetId: room.puppetId,
|
||||||
|
groupId: room.groupId,
|
||||||
|
shortDescription: p.knownGroupNames.get(room.groupId),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue