-
Notifications
You must be signed in to change notification settings - Fork 1
/
Share.lua
252 lines (222 loc) · 7.45 KB
/
Share.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
local ADDON_NAME, Internal = ...
local L = Internal.L
local External = _G[ADDON_NAME]
local format = string.format
local wipe = table.wipe
local concat = table.concat
local LibSerialize = LibStub("LibSerialize")
local LibDeflate = LibStub("LibDeflate")
local function StringToTable(text)
local stringType = text:sub(1,1)
if stringType == "{" then
local func, err = loadstring("return " .. text, "Import")
if not func then
return false, err
end
setfenv(func, {});
return pcall(func)
else
return false, L["Invalid string"]
end
end
local function TableToString(tbl)
local str = {}
for k,v in pairs(tbl) do
if type(k) == "number" then
k = "[" .. k .. "]"
elseif type(k) ~= "string" then
error(format(L["Invalid key type %s"], type(k)))
end
if type(v) == "table" then
v = TableToString(v)
elseif type(v) == "string" then
v = format("%q", v)
elseif type(v) ~= "number" and type(v) ~= "boolean" then
error(format(L["Invalid value type %s for key %s"], type(v), tostring(k)))
end
str[#str+1] = k .. "=" .. tostring(v)
end
return "{" .. concat(str, ",") .. "}"
end
local Base64Encode, Base64Decode
do
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
function Base64Encode(data)
return ((data:gsub('.', function(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data%3+1])
end
function Base64Decode(data)
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
end
local function Encode(content, format)
if #format == 1 then
if format == "N" then
return "N" .. content
elseif format == "T" then
return "T" .. TableToString(content)
elseif format == "S" then
return "S" .. LibSerialize:Serialize(content)
elseif format == "B" then
return "B" .. Base64Encode(content)
elseif format == "A" then
return "A" .. LibDeflate:EncodeForWoWAddonChannel(content)
elseif format == "Z" then
return "Z" .. LibDeflate:CompressDeflate(content)
else
error("Unsupported format")
end
elseif #format > 1 then
local f, ormat = format:match("^([A-Z])([A-Z]*)$")
return Encode(Encode(content, ormat), f)
end
end
local function Decode(content)
local format, content = content:match("^([A-Z])(.*)$")
if format == "N" then
return true, content
elseif format == "T" then
return StringToTable(content)
elseif format == "S" then
return LibSerialize:Deserialize(content)
elseif format == "B" then
return Decode(Base64Decode(content) or "")
elseif format == "A" then
return Decode(LibDeflate:DecodeForWoWAddonChannel(content) or "")
elseif format == "Z" then
return Decode(LibDeflate:DecompressDeflate(content) or "")
else
return false, "Unsupported format"
end
end
local function VerifySource(source)
local version = source.version
local sourceType = source.type
local name = source.name
if not name then
return false, L["Missing name"]
elseif type(name) ~= "string" then
return false, L["Invalid name"]
end
if sourceType ~= "todo" then
return false, L["Unsupported type"]
end
if version ~= 1 then -- Only supported version
return false, L["Invalid source version"]
end
if type(source.states) ~= "table" then
return false, L["Invalid states"]
end
local maxIndex = 0
for k,v in pairs(source.states) do
if type(k) ~= "number" then
return false, L["Invalid states"]
end
maxIndex = math.max(maxIndex, k)
end
for i=1,maxIndex do
local state = source.states[i]
if type(state) ~= "table" or type(state.type) ~= "string" then
return false, L["Invalid states"]
end
local provider = Internal.GetStateProvider(state.type)
if not provider then
return false, format(L["State provider is missing for %s"], state.type)
end
if provider:RequiresID() then
if state.id == nil then
return false, format(L["ID missing for state provider %s"], state.type)
end
if not provider:Supported(state.id, unpack(state.values or {})) then
return false, format(L["Unsupported ID and/or data for state provider %s"], state.type)
end
end
end
if type(source.completed) ~= "string" then
return false, format(L["Code error for %s function"], L["completed"])
end
if type(source.text) ~= "string" then
return false, format(L["Code error for %s function"], L["text"])
end
if type(source.click) ~= "string" then
return false, format(L["Code error for %s function"], L["click"])
end
if type(source.tooltip) ~= "string" then
return false, format(L["Code error for %s function"], L["tooltip"])
end
return true
end
function External.Import(source)
local success, err
if type(source) == "string" then
if source == "" then
return false, ""
end
success, source = Decode(source)
if not success then
return false, source
end
if type(source) == "string" then
success, source = StringToTable(source)
if not success then
return false, source
end
end
end
if type(source) ~= "table" then
return false, L["Invalid source type"]
end
success, err = VerifySource(source)
if not success then
return false, err
end
return true, source
end
function Internal.GenerateUUID()
return format("%08x-%04x-%04x-%08x", time(), math.random(0,0xffff), math.random(0,0xffff), GetTime())
end
function Internal.Export(exportType, id, format)
assert(exportType == "todo")
local todo = type(id) == "table" and id or Internal.GetTodo(id)
assert(type(todo) == "table")
if not todo.uuid then
if type(todo.id) == "number" then
todo.uuid = Internal.GenerateUUID()
else
todo.uuid = todo.id
end
end
local result = {}
result.type = "todo"
result.version = 1
result.name = todo.name
result.uuid = todo.uuid
result.states = todo.states
result.completed = todo.completed or ""
result.text = todo.text or ""
result.tooltip = todo.tooltip or ""
result.click = todo.click or ""
return Encode(result, format or "BZS")
end
function External.ExportTodo(id, format)
return Internal.Export("todo", id, format)
end