/
ChatProviderService.lua
276 lines (220 loc) · 7.74 KB
/
ChatProviderService.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
--[=[
Wrapper around Roblox chat service on the server
@class ChatProviderService
]=]
local require = require(script.Parent.loader).load(script)
local ServerScriptService = game:GetService("ServerScriptService")
local TextChatService = game:GetService("TextChatService")
local ChatTagDataUtils = require("ChatTagDataUtils")
local LocalizedTextUtils = require("LocalizedTextUtils")
local Maid = require("Maid")
local PermissionLevel = require("PermissionLevel")
local PreferredParentUtils = require("PreferredParentUtils")
local Promise = require("Promise")
local Rx = require("Rx")
local RxBrioUtils = require("RxBrioUtils")
local Signal = require("Signal")
local ChatProviderService = {}
ChatProviderService.ServiceName = "ChatProviderService"
function ChatProviderService:Init(serviceBag)
self._serviceBag = assert(serviceBag, "No serviceBag")
self._maid = Maid.new()
-- State
self.MessageIncoming = self._maid:Add(Signal.new())
-- External
self._serviceBag:GetService(require("CmdrService"))
self._serviceBag:GetService(require("PermissionService"))
self._serviceBag:GetService(require("PlayerDataStoreService"))
-- Internal
self._serviceBag:GetService(require("ChatProviderCommandService"))
self._serviceBag:GetService(require("ChatProviderTranslator"))
-- Binders
self._serviceBag:GetService(require("ChatTag"))
self._hasChatTagsBinder = self._serviceBag:GetService(require("HasChatTags"))
-- note: normally we don't expose default API surfaces like this with defaults, however, because this only affects developers and this
-- tends to significantly improve feedback we're leaving this default configuration in place.
self:SetDeveloperTag(ChatTagDataUtils.createChatTagData({
TagText = "(dev)";
LocalizedText = LocalizedTextUtils.create("chatTags.dev");
TagPriority = 15;
TagColor = Color3.fromRGB(245, 163, 27);
}))
self:SetAdminTag(ChatTagDataUtils.createChatTagData({
TagText = "(mod)";
LocalizedText = LocalizedTextUtils.create("chatTags.mod");
TagPriority = 10;
TagColor = Color3.fromRGB(78, 205, 196);
}))
end
function ChatProviderService:AddChatCommand(textChatCommand)
assert(typeof(textChatCommand) == "Instance", "Bad textChatCommand")
textChatCommand.Parent = PreferredParentUtils.getPreferredParent(TextChatService, "ChatProviderCommands")
end
--[=[
Sets the developer chat tag
@param chatTagData ChatTagData | nil
@return Maid
]=]
function ChatProviderService:SetDeveloperTag(chatTagData)
assert(ChatTagDataUtils.isChatTagData(chatTagData) or chatTagData == nil, "Bad chatTagData")
if chatTagData then
local permissionService = self._serviceBag:GetService(require("PermissionService"))
local observeBrio = permissionService:ObservePermissionedPlayersBrio(PermissionLevel.CREATOR)
self._maid._developer = self:_addObservablePlayerTag(observeBrio, chatTagData)
else
self._maid._developer = nil
end
end
--[=[
Sets the admin tag to the game
@param chatTagData ChatTagData | nil
@return Maid
]=]
function ChatProviderService:SetAdminTag(chatTagData)
assert(ChatTagDataUtils.isChatTagData(chatTagData) or chatTagData == nil, "Bad chatTagData")
if chatTagData then
local permissionService = self._serviceBag:GetService(require("PermissionService"))
local observeBrio = permissionService:ObservePermissionedPlayersBrio(PermissionLevel.ADMIN):Pipe({
RxBrioUtils.flatMapBrio(function(player)
return Rx.fromPromise(permissionService:PromiseIsPermissionLevel(player, PermissionLevel.CREATOR))
:Pipe({
Rx.switchMap(function(isAlsoCreator)
if not isAlsoCreator then
return Rx.of(player)
else
return Rx.EMPTY
end
end)
})
end)
})
self._maid._admin = self:_addObservablePlayerTag(observeBrio, chatTagData)
else
self._maid._admin = nil
end
end
function ChatProviderService:_addObservablePlayerTag(observePlayersBrio, chatTagData)
assert(ChatTagDataUtils.isChatTagData(chatTagData), "Bad chatTagData")
local topMaid = Maid.new()
self._maid[topMaid] = topMaid
topMaid:GiveTask(function()
self._maid[topMaid] = nil
end)
topMaid:GiveTask(observePlayersBrio:Subscribe(function(brio)
if brio:IsDead() then
return
end
local maid = brio:ToMaid()
local player = brio:GetValue()
maid:GivePromise(self:PromiseAddChatTag(player, chatTagData))
:Then(function(chatTag)
maid:GiveTask(chatTag)
end)
end))
return topMaid
end
--[=[
Promises to add a chat tag to the player
@param player Player
@param chatTagData ChatTagData
@return Promise<Instance>
]=]
function ChatProviderService:PromiseAddChatTag(player, chatTagData)
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
assert(ChatTagDataUtils.isChatTagData(chatTagData), "Bad chatTagData")
local hasChatTagBinder = self._serviceBag:GetService(require("HasChatTags"))
return hasChatTagBinder:Promise(player)
:Then(function(hasChatTag)
return hasChatTag:AddChatTag(chatTagData)
end)
end
--[=[
Clears the player's chat chatTagData.
@param player Player
]=]
function ChatProviderService:ClearChatTags(player)
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
local hasChatTagBinder = self._serviceBag:GetService(require("HasChatTags"))
local hasChatTags = hasChatTagBinder:Get(player)
if hasChatTags then
hasChatTags:ClearTags()
end
end
--[=[
Sets the speaker's tag (by speaker name)
@param speakerName string
@param chatTagDataList { ChatTagData }
]=]
function ChatProviderService:PromiseSetSpeakerTags(speakerName, chatTagDataList)
assert(type(speakerName) == "string", "Bad speakerName")
assert(ChatTagDataUtils.isChatTagDataList(chatTagDataList))
return self:_promiseSpeaker(speakerName)
:Then(function(speaker)
if not speaker then
return nil
end
speaker:SetExtraData("Tags", chatTagDataList)
end, function(err)
warn("[ChatProviderService.PromiseSetTags] - No speaker found", err)
end)
end
function ChatProviderService:_getChatServiceAsync()
if self._chatService then
return self._chatService
end
local chatServiceRunner = ServerScriptService:WaitForChild("ChatServiceRunner", 5)
if not chatServiceRunner then
-- Presumably we have upgraded to the new chat.
return nil
end
local chatService = require(chatServiceRunner:WaitForChild("ChatService"))
self._chatService = chatService or error("No chatService retrieved")
return self._chatService
end
function ChatProviderService:_promiseChatService()
if self._chatService then
return Promise.resolved(self._chatService)
end
if self._chatServicePromise then
return Promise.resolved(self._chatServicePromise)
end
self._chatServicePromise = Promise.defer(function(resolve, _reject)
resolve(self:_getChatServiceAsync())
end)
return Promise.resolved(self._chatServicePromise)
end
function ChatProviderService:_promiseSpeaker(speakerName)
assert(type(speakerName) == "string", "Bad speakerName")
return self:_promiseChatService():Then(function(chatService)
if not chatService then
return nil
end
local foundSpeaker = chatService:GetSpeaker(speakerName)
if foundSpeaker then
return foundSpeaker
end
local promise = Promise.new()
local maid = Maid.new()
-- TODO: Avoid memory leaking
maid:GiveTask(task.delay(5, function()
warn("[ChatProviderService._promiseSpeaker] - Infinite yield possible for speaker")
end))
-- Listen to speaker added
maid:GiveTask(chatService.SpeakerAdded:Connect(function(speakerAddedName)
if speakerName == speakerAddedName then
local speaker = chatService:GetSpeaker(speakerName)
if not speaker then
warn("[ChatProviderService._promiseSpeaker] - Speaker added, but no speaker added")
promise:Reject("Speaker added, but no speaker added")
else
promise:Resolve(speaker)
end
end
end))
promise:Finally(function()
maid:DoCleaning()
end)
return promise
end)
end
return ChatProviderService