Added staff chat channels.

This commit is contained in:
Loudrob 2015-03-31 07:59:36 -04:00
parent 5b1136a11b
commit c72e45fbc4
9 changed files with 243 additions and 33 deletions

View file

@ -255,8 +255,10 @@ dclass MagicWordManager : DistributedObject {
}; };
dclass ChatAgent : DistributedObject { dclass ChatAgent : DistributedObject {
adminChat(uint32, string); adminChat(uint32 aboutId, string message);
chatMessage(string(0-256)) clsend; chatMessage(string(0-256) message, uint8 chatMode) clsend;
whisperMessage(uint32 receiverAvId, string(0-256) message) clsend;
sfWhisperMessage(uint32 receiverAvId, string(0-256) message) clsend;
}; };
dclass FriendManager : DistributedObject { dclass FriendManager : DistributedObject {

View file

@ -1,14 +1,17 @@
from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal
from pandac.PandaModules import * from pandac.PandaModules import *
from otp.otpbase import OTPGlobals from otp.otpbase import OTPGlobals
from otp.ai.MagicWordGlobal import *
class ChatAgent(DistributedObjectGlobal): class ChatAgent(DistributedObjectGlobal):
def __init__(self, cr): def __init__(self, cr):
DistributedObjectGlobal.__init__(self, cr) DistributedObjectGlobal.__init__(self, cr)
self.chatMode = 0
def delete(self): def delete(self):
self.ignoreAll() self.ignoreAll()
self.cr.chatManager = None self.cr.chatManager = None
self.cr.chatAgent = None
DistributedObjectGlobal.delete(self) DistributedObjectGlobal.delete(self)
return return
@ -17,4 +20,35 @@ class ChatAgent(DistributedObjectGlobal):
messenger.send('adminChat', [aboutId, message]) messenger.send('adminChat', [aboutId, message])
def sendChatMessage(self, message): def sendChatMessage(self, message):
self.sendUpdate('chatMessage', [message]) self.sendUpdate('chatMessage', [message, self.chatMode])
def sendWhisperMessage(self, receiverAvId, message):
self.sendUpdate('whisperMessage', [receiverAvId, message])
def sendSFWhisperMessage(self, receiverAvId, message):
self.sendUpdate('sfWhisperMessage', [receiverAvId, message])
@magicWord(category=CATEGORY_MODERATOR, types=[int])
def chatmode(mode=-1):
""" Set the chat mode of the current avatar. """
mode2name = {
0 : "user",
1 : "moderator",
2 : "administrator",
3 : "system administrator",
}
if base.cr.chatAgent is None:
return "No ChatAgent found."
if mode == -1:
return "You are currently talking in the %s chat mode." % mode2name.get(base.cr.chatAgent.chatMode, "N/A")
if not 0 <= mode <= 3:
return "Invalid chat mode specified."
if mode == 3 and spellbook.getInvoker().getAdminAccess() < 500:
return "Chat mode 3 is reserved for system administrators."
if mode == 2 and spellbook.getInvoker().getAdminAccess() < 400:
return "Chat mode 2 is reserved for administrators."
if mode == 1 and spellbook.getInvoker().getAdminAccess() < 200:
# Like this will ever happen, but whatever.
return "Chat mode 1 is reserved for moderators."
base.cr.chatAgent.chatMode = mode
return "You are now talking in the %s chat mode." % mode2name.get(mode, "N/A")

View file

@ -2,41 +2,151 @@ from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
# TODO: OTP should not depend on Toontown... Hrrm. # TODO: OTP should not depend on Toontown... Hrrm.
from toontown.chat.TTWhiteList import TTWhiteList from toontown.chat.TTWhiteList import TTWhiteList
from toontown.chat.TTSequenceList import TTSequenceList
from otp.distributed import OtpDoGlobals
import time
class ChatAgentUD(DistributedObjectGlobalUD): class ChatAgentUD(DistributedObjectGlobalUD):
notify = DirectNotifyGlobal.directNotify.newCategory("ChatAgentUD") notify = DirectNotifyGlobal.directNotify.newCategory("ChatAgentUD")
def announceGenerate(self): def announceGenerate(self):
DistributedObjectGlobalUD.announceGenerate(self) DistributedObjectGlobalUD.announceGenerate(self)
self.wantBlacklistSequence = config.GetBool('want-blacklist-sequence', True)
self.wantWhitelist = config.GetBool('want-whitelist', True)
if self.wantWhitelist:
self.whiteList = TTWhiteList()
if self.wantBlacklistSequence:
self.sequenceList = TTSequenceList()
self.chatMode2channel = {
1 : OtpDoGlobals.OTP_MOD_CHANNEL,
2 : OtpDoGlobals.OTP_ADMIN_CHANNEL,
3 : OtpDoGlobals.OTP_SYSADMIN_CHANNEL,
}
self.chatMode2prefix = {
1 : "[MOD] ",
2 : "[ADMIN] ",
3 : "[SYSADMIN] ",
}
self.muted = {}
self.whiteList = TTWhiteList() def muteAccount(self, account, howLong):
print ['muteAccount', account, howLong]
self.muted[account] = int(time.time()/60) + howLong
def chatMessage(self, message): def unmuteAccount(self, account):
print ['unmuteAccount', account]
if account in self.muted:
del self.muted[account]
# Open chat
def chatMessage(self, message, chatMode):
sender = self.air.getAvatarIdFromSender() sender = self.air.getAvatarIdFromSender()
if sender == 0: if sender == 0:
self.air.writeServerEvent('suspicious', self.air.getAccountIdFromSender(), self.air.writeServerEvent('suspicious', accId=self.air.getAccountIdFromSender(),
'Account sent chat without an avatar', message) issue='Account sent chat without an avatar', message=message)
return
if sender in self.muted and int(time.time()/60) < self.muted[sender]:
return
if self.wantWhitelist:
cleanMessage, modifications = self.cleanWhitelist(message)
else:
cleanMessage, modifications = message, []
self.air.writeServerEvent('chat-said', avId=sender, chatMode=chatMode, msg=message, cleanMsg=cleanMessage)
# TODO: The above is probably a little too ugly for my taste... Maybe AIR
# should be given an API for sending updates for unknown objects?
if chatMode != 0:
# Staff messages do not need to be cleaned. [TODO: Blacklist this?]
if message.startswith('.'):
# This is a thought bubble, move the point to the start.
cleanMessage = '.' + self.chatMode2prefix.get(chatMode, "") + message[1:]
else:
cleanMessage = self.chatMode2prefix.get(chatMode, "") + message
modifications = []
DistributedAvatar = self.air.dclassesByName['DistributedAvatarUD']
dg = DistributedAvatar.aiFormatUpdate('setTalk', sender, self.chatMode2channel.get(chatMode, sender),
self.air.ourChannel,
[0, 0, '', cleanMessage, modifications, 0])
self.air.send(dg)
self.air.csm.accountDB.persistChat(sender, message, self.air.ourChannel)
# Regular filtered chat
def whisperMessage(self, receiverAvId, message):
sender = self.air.getAvatarIdFromSender()
if sender == 0:
self.air.writeServerEvent('suspicious', accId=self.air.getAccountIdFromSender(),
issue='Account sent chat without an avatar', message=message)
return return
cleanMessage, modifications = self.cleanWhitelist(message)
# Maybe a better "cleaner" way of doing this, but it works
self.air.writeServerEvent('whisper-said', avId=sender, reciever=receiverAvId, msg=message, cleanMsg=cleanMessage)
DistributedAvatar = self.air.dclassesByName['DistributedAvatarUD']
dg = DistributedAvatar.aiFormatUpdate('setTalkWhisper', receiverAvId, receiverAvId, self.air.ourChannel,
[sender, sender, '', cleanMessage, modifications, 0])
self.air.send(dg)
# True friend unfiltered chat
def sfWhisperMessage(self, receiverAvId, message):
sender = self.air.getAvatarIdFromSender()
if sender == 0:
self.air.writeServerEvent('suspicious', accId=self.air.getAccountIdFromSender(),
issue='Account sent chat without an avatar', message=message)
return
cleanMessage = self.cleanBlacklist(message)
self.air.writeServerEvent('sf-whisper-said', avId=sender, reciever=receiverAvId, msg=message, cleanMsg=cleanMessage)
DistributedAvatar = self.air.dclassesByName['DistributedAvatarUD']
dg = DistributedAvatar.aiFormatUpdate('setTalkWhisper', receiverAvId, receiverAvId, self.air.ourChannel,
[sender, sender, '', cleanMessage, [], 0])
self.air.send(dg)
# Filter the chat message
def cleanWhitelist(self, message):
modifications = [] modifications = []
words = message.split(' ') words = message.split(' ')
offset = 0 offset = 0
WantWhitelist = config.GetBool('want-whitelist', 1)
for word in words: for word in words:
if word and not self.whiteList.isWord(word) and WantWhitelist: if word and not self.whiteList.isWord(word):
modifications.append((offset, offset+len(word)-1)) modifications.append((offset, offset+len(word)-1))
offset += len(word) + 1 offset += len(word) + 1
cleanMessage = message cleanMessage = message
if self.wantBlacklistSequence:
modifications += self.cleanSequences(cleanMessage)
for modStart, modStop in modifications: for modStart, modStop in modifications:
cleanMessage = cleanMessage[:modStart] + '*'*(modStop-modStart+1) + cleanMessage[modStop+1:] # Traverse through modification list and replace the characters of non-whitelisted words and/or blacklisted sequences with asterisks.
cleanMessage = cleanMessage[:modStart] + '*' * (modStop - modStart + 1) + cleanMessage[modStop + 1:]
self.air.writeServerEvent('chat-said', sender, message, cleanMessage) return (cleanMessage, modifications)
# TODO: The above is probably a little too ugly for my taste... Maybe AIR # Check the black list for black-listed words
# should be given an API for sending updates for unknown objects? def cleanBlacklist(self, message):
DistributedAvatar = self.air.dclassesByName['DistributedAvatarUD'] # We don't have a black list so we just return the full message
dg = DistributedAvatar.aiFormatUpdate('setTalk', sender, sender, return message
self.air.ourChannel,
[0, 0, '', cleanMessage, modifications, 0]) # Check for black-listed word sequences and scrub accordingly.
self.air.send(dg) def cleanSequences(self, message):
modifications = []
offset = 0
words = message.split()
for wordit in xrange(len(words)):
word = words[wordit].lower()
seqlist = self.sequenceList.getList(word)
if len(seqlist) > 0:
for seqit in xrange(len(seqlist)):
sequence = seqlist[seqit]
splitseq = sequence.split()
if len(words) - (wordit + 1) >= len(splitseq):
cmplist = words[wordit + 1:]
del cmplist[len(splitseq):]
cmplist = [word.lower() for word in cmplist]
if cmp(cmplist, splitseq) == 0:
modifications.append((offset, offset + len(word) + len(sequence) - 1))
offset += len(word) + 1
return modifications

15
otp/chat/SequenceList.py Normal file
View file

@ -0,0 +1,15 @@
class SequenceList:
def __init__(self, wordlist):
self.list = {}
for line in wordlist:
if line is '':
continue
split = line.split(':')
self.list[split[0].lower()] = [word.rstrip('\r\n').lower() for word in split[1].split(',')]
def getList(self, word):
if word in self.list:
return self.list[word]
else:
return []

View file

@ -1,22 +1,55 @@
from bisect import bisect_left from bisect import bisect_left
import string
import sys
import os
class WhiteList: class WhiteList:
def __init__(self, words):
self.words = words def __init__(self, wordlist):
self.words = []
for line in wordlist:
self.words.append(line.strip('\n\r').lower())
self.words.sort()
self.numWords = len(self.words) self.numWords = len(self.words)
def cleanText(self, text): def cleanText(self, text):
text = text.strip('.,?!') text = text.strip('.,?!')
return text.lower() text = text.lower()
return text
def isWord(self, text): def isWord(self, text):
return self.cleanText(text) in self.words try:
text = self.cleanText(text)
i = bisect_left(self.words, text)
if i == self.numWords:
return False
return self.words[i] == text
except UnicodeDecodeError:
return False # Lets not open ourselves up to obscure keyboards...
def isPrefix(self, text): def isPrefix(self, text):
text = self.cleanText(text) text = self.cleanText(text)
i = bisect_left(self.words, text) i = bisect_left(self.words, text)
if i == self.numWords: if i == self.numWords:
return False return False
return self.words[i].startswith(text)
return self.words[i].startswith(text) def prefixCount(self, text):
text = self.cleanText(text)
i = bisect_left(self.words, text)
j = i
while j < self.numWords and self.words[j].startswith(text):
j += 1
return j - i
def prefixList(self, text):
text = self.cleanText(text)
i = bisect_left(self.words, text)
j = i
while j < self.numWords and self.words[j].startswith(text):
j += 1
return self.words[i:j]

View file

@ -2,7 +2,7 @@
from pandac.PandaModules import * from pandac.PandaModules import *
hashVal = 598642574 hashVal = 4270694562L
from toontown.coghq import DistributedCashbotBossSafe, DistributedCashbotBossCrane, DistributedBattleFactory, DistributedCashbotBossTreasure, DistributedCogHQDoor, DistributedSellbotHQDoor, DistributedFactoryElevatorExt, DistributedMintElevatorExt, DistributedLawOfficeElevatorExt, DistributedLawOfficeElevatorInt, LobbyManager, DistributedMegaCorp, DistributedFactory, DistributedLawOffice, DistributedLawOfficeFloor, DistributedLift, DistributedDoorEntity, DistributedSwitch, DistributedButton, DistributedTrigger, DistributedCrushableEntity, DistributedCrusherEntity, DistributedStomper, DistributedStomperPair, DistributedLaserField, DistributedGolfGreenGame, DistributedSecurityCamera, DistributedMover, DistributedElevatorMarker, DistributedBarrelBase, DistributedGagBarrel, DistributedBeanBarrel, DistributedHealBarrel, DistributedGrid, ActiveCell, DirectionalCell, CrusherCell, DistributedCrate, DistributedSinkingPlatform, BattleBlocker, DistributedMint, DistributedMintRoom, DistributedMintBattle, DistributedStage, DistributedStageRoom, DistributedStageBattle, DistributedLawbotBossGavel, DistributedLawbotCannon, DistributedLawbotChair, DistributedCogKart, DistributedCountryClub, DistributedCountryClubRoom, DistributedMoleField, DistributedCountryClubBattle, DistributedMaze, DistributedFoodBelt, DistributedBanquetTable, DistributedGolfSpot from toontown.coghq import DistributedCashbotBossSafe, DistributedCashbotBossCrane, DistributedBattleFactory, DistributedCashbotBossTreasure, DistributedCogHQDoor, DistributedSellbotHQDoor, DistributedFactoryElevatorExt, DistributedMintElevatorExt, DistributedLawOfficeElevatorExt, DistributedLawOfficeElevatorInt, LobbyManager, DistributedMegaCorp, DistributedFactory, DistributedLawOffice, DistributedLawOfficeFloor, DistributedLift, DistributedDoorEntity, DistributedSwitch, DistributedButton, DistributedTrigger, DistributedCrushableEntity, DistributedCrusherEntity, DistributedStomper, DistributedStomperPair, DistributedLaserField, DistributedGolfGreenGame, DistributedSecurityCamera, DistributedMover, DistributedElevatorMarker, DistributedBarrelBase, DistributedGagBarrel, DistributedBeanBarrel, DistributedHealBarrel, DistributedGrid, ActiveCell, DirectionalCell, CrusherCell, DistributedCrate, DistributedSinkingPlatform, BattleBlocker, DistributedMint, DistributedMintRoom, DistributedMintBattle, DistributedStage, DistributedStageRoom, DistributedStageBattle, DistributedLawbotBossGavel, DistributedLawbotCannon, DistributedLawbotChair, DistributedCogKart, DistributedCountryClub, DistributedCountryClubRoom, DistributedMoleField, DistributedCountryClubBattle, DistributedMaze, DistributedFoodBelt, DistributedBanquetTable, DistributedGolfSpot

View file

@ -88,4 +88,6 @@ OTP_ZONE_ID_DISTRICTS = 3
OTP_ZONE_ID_DISTRICTS_STATS = 4 OTP_ZONE_ID_DISTRICTS_STATS = 4
OTP_ZONE_ID_ELEMENTS = 5 OTP_ZONE_ID_ELEMENTS = 5
OTP_NET_MESSENGER_CHANNEL = (OTP_DO_ID_UBER_DOG << 32) + OTP_ZONE_ID_MANAGEMENT OTP_NET_MESSENGER_CHANNEL = (OTP_DO_ID_UBER_DOG << 32) + OTP_ZONE_ID_MANAGEMENT
OTP_STAFF_CHANNEL = 6200 OTP_MOD_CHANNEL = 6200
OTP_ADMIN_CHANNEL = 6400
OTP_SYSADMIN_CHANNEL = 6500

View file

@ -35,6 +35,7 @@ if args.astron_ip: localconfig += 'air-connect %s\n' % args.astron_ip
if args.eventlogger_ip: localconfig += 'eventlog-host %s\n' % args.eventlogger_ip if args.eventlogger_ip: localconfig += 'eventlog-host %s\n' % args.eventlogger_ip
loadPrcFileData('Command-line', localconfig) loadPrcFileData('Command-line', localconfig)
from otp.ai.AIBaseGlobal import * from otp.ai.AIBaseGlobal import *
from toontown.ai.ToontownAIRepository import ToontownAIRepository from toontown.ai.ToontownAIRepository import ToontownAIRepository

View file

@ -422,13 +422,26 @@ class LoginAccountFSM(OperationFSM):
datagram.addChannel(self.csm.GetAccountConnectionChannel(self.accountId)) datagram.addChannel(self.csm.GetAccountConnectionChannel(self.accountId))
self.csm.air.send(datagram) self.csm.air.send(datagram)
# Add this connection to extra channels which may be useful: # Subscribe to any "staff" channels that the account has access to.
if self.accessLevel > 100: access = self.account.get('ADMIN_ACCESS', 0)
datagram = PyDatagram() if access >= 200:
datagram.addServerHeader(self.target, self.csm.air.ourChannel, # Subscribe to the moderator channel.
CLIENTAGENT_OPEN_CHANNEL) dg = PyDatagram()
datagram.addChannel(OtpDoGlobals.OTP_STAFF_CHANNEL) dg.addServerHeader(self.target, self.csm.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL)
self.csm.air.send(datagram) dg.addChannel(OtpDoGlobals.OTP_MOD_CHANNEL)
self.csm.air.send(dg)
if access >= 400:
# Subscribe to the administrator channel.
dg = PyDatagram()
dg.addServerHeader(self.target, self.csm.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL)
dg.addChannel(OtpDoGlobals.OTP_ADMIN_CHANNEL)
self.csm.air.send(dg)
if access >= 500:
# Subscribe to the system administrator channel.
dg = PyDatagram()
dg.addServerHeader(self.target, self.csm.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL)
dg.addChannel(OtpDoGlobals.OTP_SYSADMIN_CHANNEL)
self.csm.air.send(dg)
# Now set their sender channel to represent their account affiliation: # Now set their sender channel to represent their account affiliation:
datagram = PyDatagram() datagram = PyDatagram()