vrtc / flowerpicker (public) (License: GPLv3) (since 2019-11-07) (hash sha1)
Flowerpicker is a GUI extension, that is an add-on, for World of Warcraft game client. It is under development and not yet ready for usage by players.
List of commits:
Subject Hash Author Date (UTC)
Update event format 554e63f3490dc48befe37fbfacf5c8c9ad188451 Vladyslav Bondarenko 2019-11-06 09:59:18
Initial commit f014031ddd4b2422ef5d000f33ad6602e64c9b36 Vladyslav Bondarenko 2019-11-05 20:29:14
Commit 554e63f3490dc48befe37fbfacf5c8c9ad188451 - Update event format
All events register game zone and game subzone for future reference.

Additionally, event occurrence date and time includes timezone offset.
There is a complication regarding timezone offset inclusion.
`os.date` format option `%z`, as in `strftime`, is platform dependent.
Therefore, to ensure that registered event timestamp data is portable,
a third-party snippet was included in the code.

An error in name validation code is fixed.
Author: Vladyslav Bondarenko
Author date (UTC): 2019-11-06 09:59
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2019-11-06 09:59
Parent(s): f014031ddd4b2422ef5d000f33ad6602e64c9b36
Signing key:
Tree: 605fc2084b7bded4e6ee94b33876a4575f46988c
File Lines added Lines deleted
Flowerpicker.lua 97 48
File Flowerpicker.lua changed (mode: 100644) (index 3f5c528..dbb5427)
1 --[[ begin http://lua-users.org/wiki/TimeZone ]]--
2 -- Compute the difference in seconds between local time and UTC.
3 local function get_timezone()
4 local now = time()
5 return difftime(now, time(date("!*t", now)))
6 end
7 -- timezone = get_timezone()
8
9 -- Return a timezone string in ISO 8601:2000 standard form (+hhmm or -hhmm)
10 local function get_tzoffset(timezone)
11 local h, m = math.modf(timezone / 3600)
12 return string.format("%+.4d", 100 * h + 60 * m)
13 end
14 -- tzoffset = get_tzoffset(timezone)
15
16
17 --[[ debugging
18 for _, tz in ipairs(arg) do
19 if tz == '-' then
20 tz = timezone
21 else
22 tz = 0 + tz
23 end
24 print(tz, get_tzoffset(tz))
25 end
26 --]]
27
28 -- return the timezone offset in seconds, as it was on the time given by ts
29 -- Eric Feliksik
30 local function get_timezone_offset(ts)
31 local utcdate = date("!*t", ts)
32 local localdate = date("*t", ts)
33 localdate.isdst = false -- this is the trick
34 return difftime(time(localdate), time(utcdate))
35 end
36 --[[ end http://lua-users.org/wiki/TimeZone ]]--
37
1 38 --[[ Security, reliability. ]]-- --[[ Security, reliability. ]]--
2 39
3 40 local function sanitiseShort(supposedNumber) local function sanitiseShort(supposedNumber)
 
... ... local function sanitiseTimestamp(supposedTimestamp)
16 53 local sane = nil local sane = nil
17 54 if nil == supposedTimestamp then if nil == supposedTimestamp then
18 55 elseif 'string' == type(supposedTimestamp) then elseif 'string' == type(supposedTimestamp) then
19 sane = string.sub(strtrim(supposedTimestamp), 1, 19)
56 sane = string.sub(strtrim(supposedTimestamp), 1, 24)
20 57 else else
21 58 sane = nil sane = nil
22 59 end end
 
... ... local function validateAnyName(supposedName, lengthMin, lengthMax)
71 108 end end
72 109
73 110 local len = string.len(supposedName) local len = string.len(supposedName)
74 isValid = (len >= lengthMin or len <= lengthMax) and isValid
111 isValid = (len >= lengthMin and len <= lengthMax) and isValid
75 112 if not isValid then if not isValid then
76 113 errorMsg = string.format('String length %d not in [%d, %d].', len, lengthMin, lengthMax) errorMsg = string.format('String length %d not in [%d, %d].', len, lengthMin, lengthMax)
77 114 return isValid, errorMsg return isValid, errorMsg
 
... ... local function validateTimestamp(t)
84 121 local isValid = true local isValid = true
85 122 local errorMsg = nil local errorMsg = nil
86 123
87 isValid, errorMsg = validateAnyName(t, 19, 19)
124 isValid, errorMsg = validateAnyName(t, 24, 24)
88 125 if not isValid then if not isValid then
89 126 return isValid, errorMsg return isValid, errorMsg
90 127 end end
91 128
92 129 --[[ --[[
93 -- os.date('%Y-%m-%d %H:%M:%S', os.time())
94 -- ex, 2019-11-05 14:28:30
130 -- os.date('%Y-%m-%d %H:%M:%S%z', os.time())
131 -- ex, 2019-11-05 14:28:30+0200
95 132 ]]-- ]]--
96 isValid = t == string.match(t, '%d%d%d%d--%d%d--%d%d %d%d:%d%d:%d%d') and isValid
133 isValid = t == string.match(t, '%d%d%d%d--%d%d--%d%d %d%d:%d%d:%d%d++%d%d%d%d') and isValid
97 134 if not isVallid then if not isVallid then
98 135 errorMsg = 'Is not a timestamp.' errorMsg = 'Is not a timestamp.'
99 136 return isValid, errorMsg return isValid, errorMsg
 
... ... local function isPermissibleSpellToProduceLoot(spellName)
178 215 return result return result
179 216 end end
180 217
181 local function createEventHarvest(harvestTypeDesignation, realmName, harvesterName, sourceName, harvestTimestamp, harvestedItemName, harvestedItemQuantity)
182 local d = sanitiseAnyName(harvestTypeDesignation)
218 local function createEventHarvest(realmName, harvesterName, zoneName, subzoneName, harvestTimestamp, harvestTypeDesignation, sourceName, harvestedItemName, harvestedItemQuantity)
183 219 local r = sanitiseAnyName(realmName) local r = sanitiseAnyName(realmName)
184 220 local c = sanitiseAnyName(harvesterName) local c = sanitiseAnyName(harvesterName)
185 local s = sanitiseAnyName(sourceName)
221 local z = sanitiseAnyName(zoneName)
222 local sz = sanitiseAnyName(subzoneName)
186 223 local t = sanitiseTimestamp(harvestTimestamp) local t = sanitiseTimestamp(harvestTimestamp)
224 local d = sanitiseAnyName(harvestTypeDesignation)
225 local s = sanitiseAnyName(sourceName)
187 226 local i = sanitiseAnyName(harvestedItemName) local i = sanitiseAnyName(harvestedItemName)
188 227 local q = sanitiseShort(harvestedItemQuantity) local q = sanitiseShort(harvestedItemQuantity)
189 assert (validateEnum(d, getPermissibleHarvestTypeSet()))
190 228 assert (validateAnyName(r)) assert (validateAnyName(r))
191 229 assert (validateAnyName(c)) assert (validateAnyName(c))
192 assert (validateAnyName(s))
230 assert (validateAnyName(z))
231 assert (validateAnyName(sz))
193 232 assert (validateTimestamp(t)) assert (validateTimestamp(t))
233 assert (validateEnum(d, getPermissibleHarvestTypeSet()))
234 assert (validateAnyName(s))
194 235 assert (validateAnyName(i)) assert (validateAnyName(i))
195 236 assert (validatePositiveAndNonZero(q)) assert (validatePositiveAndNonZero(q))
196 237
197 238 local event = { local event = {
198 d, r, c, s, t, i, q
239 r, c, z, sz, t, d, s, i, q
199 240 } }
200 241
201 242 assert (event ~= nil) assert (event ~= nil)
 
... ... local function updateLootCache()
243 284 end end
244 285 end end
245 286
287 local function processLoot(slotId, lastSpellCastName, lootedCorpseGuessedName)
288 local spellName = sanitiseAnyName(lastSpellCastName)
289 local d
290 local s
291 if isPermissibleSpellToProduceLoot(spellName) then
292 assert (validateAnyName(spellName))
293 d = mapSpellNameToHarvestTypeDesignation(spellName)
294 else
295 d = 'LOOTCORPSE'
296 s = sanitiseAnyName(lootedCorpseGuessedName)
297 assert (validateAnyName(s))
298 end
299 assert (validateEnum(d, getPermissibleHarvestTypeSet()))
300 local r = sanitiseAnyName(GetRealmName())
301 assert (validateAnyName(r))
302 local p = sanitiseAnyName(UnitName('player'))
303 assert (validateAnyName(p))
304 local t = date('%Y-%m-%d %H:%M:%S', time()) .. get_tzoffset(get_timezone())
305 local lootCache = FlowerpickerLootCache
306 assert (lootCache ~= nil)
307 assert ('table' == type(lootCache))
308 local lootedItem = lootCache[slotId]
309 assert (lootedItem ~= nil)
310 assert ('table' == type(lootedItem))
311 local i = sanitiseAnyName(lootedItem[2])
312 assert (validateAnyName(i))
313 local q = sanitiseShort(lootedItem[3])
314 assert (validatePositiveAndNonZero(q))
315 local z = sanitiseAnyName(GetZoneText())
316 if nil == z or string.len(z) <= 0 then
317 z = 'Unknown Zone'
318 end
319 local sz = sanitiseAnyName(GetSubZoneText())
320 if nil == sz or string.len(sz) <= 0 then
321 sz = 'Unknown Subzone'
322 end
323 local e = createEventHarvest(r, p, z, sz, t, d, s, i, q)
324 table.insert(FlowerpickerSavedVariables, e)
325 end
326
246 327 local function main(self, event, arg1, arg2, arg3, arg4, arg5) local function main(self, event, arg1, arg2, arg3, arg4, arg5)
247 328 assert (self ~= nil) assert (self ~= nil)
248 329 assert ('table' == type(self)) assert ('table' == type(self))
 
... ... local function main(self, event, arg1, arg2, arg3, arg4, arg5)
264 345 local slotId = sanitiseShort(arg1) local slotId = sanitiseShort(arg1)
265 346 assert (slotId ~= nil) assert (slotId ~= nil)
266 347 assert ('number' == type(slotId)) assert ('number' == type(slotId))
267 local spellName = sanitiseAnyName(self.api.lastSpellCastName)
268 local d
269 local s
270 if isPermissibleSpellToProduceLoot(spellName) then
271 assert (validateAnyName(spellName))
272 d = mapSpellNameToHarvestTypeDesignation(spellName)
273 else
274 d = 'LOOTCORPSE'
275 s = sanitiseAnyName(self.api.lootedCorpseGuessedName)
276 assert (validateAnyName(s))
277 end
278 assert (validateEnum(d, getPermissibleHarvestTypeSet()))
279 local r = sanitiseAnyName(GetRealmName())
280 assert (validateAnyName(r))
281 local p = sanitiseAnyName(UnitName('player'))
282 assert (validateAnyName(p))
283 local t = date('%Y-%m-%d %H:%M:%S', time())
284 local lootCache = FlowerpickerLootCache
285 assert (lootCache ~= nil)
286 assert ('table' == type(lootCache))
287 local lootedItem = lootCache[slotId]
288 assert (lootedItem ~= nil)
289 assert ('table' == type(lootedItem))
290 local i = sanitiseAnyName(lootedItem[2])
291 assert (validateAnyName(i))
292 local q = sanitiseShort(lootedItem[3])
293 assert (validatePositiveAndNonZero(q))
294 local e = createEventHarvest(d, r, p, s, t, i, q)
295 table.insert(FlowerpickerSavedVariables, e)
296 print(d, r, p, s, t, i, q)
297 --updateLootCache()
298 --[[local flowerpickerEvent = createEventHarvest(
299 )
300 FlowerpickerSavedVariables[] = flowerpickerEvent]]--
301 elseif 'LOOT_SLOT_CHANGED' == event then
302 print(event, arg1, arg2, arg3)
348 local lastSpellCastName = sanitiseAnyName(self.api.lastSpellCastName)
349 local corpseName = sanitiseAnyName(self.api.lootedCorpseGuessedName)
350 processLoot(slotId, lastSpellCastName, corpseName)
303 351 elseif 'LOOT_CLOSED' == event then elseif 'LOOT_CLOSED' == event then
304 352 FlowerpickerLootCache = {} FlowerpickerLootCache = {}
305 353 self.api.lootedCorpseGuessedName = nil self.api.lootedCorpseGuessedName = nil
 
... ... local function init()
337 385 local api = { local api = {
338 386 ['createEventHarvest'] = createEventHarvest, ['createEventHarvest'] = createEventHarvest,
339 387 ['lootCache'] = lootCache, ['lootCache'] = lootCache,
388 ['lootedCorpseGuessedName'] = nil,
340 389 ['lastSpellCastName'] = nil ['lastSpellCastName'] = nil
341 390 } }
342 391 addonFrame.api = api addonFrame.api = api
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/vrtc/flowerpicker

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/vrtc/flowerpicker

Clone this repository using git:
git clone git://git.rocketgit.com/user/vrtc/flowerpicker

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main