Advertisement
Guest User

Untitled

a guest
Feb 1st, 2016
154
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 27.24 KB | None | 0 0
  1. function Initialize()
  2. -- SET UPDATE DIVIDER
  3. SKIN:Bang('!SetOption', SELF:GetName(), 'UpdateDivider', -1)
  4. -- This script never needs to update on a schedule. It should only
  5. -- update when it gets a "Refresh" command from WebParser.
  6.  
  7. -- CREATE MAIN DATABASE
  8. Feeds = {}
  9.  
  10. -- CREATE TYPE MATCHING PATTERNS AND FORMATTING FUNCTIONS
  11. DefineTypes()
  12.  
  13. -- GET MEASURE NAMES
  14. local AllMeasureNames = SELF:GetOption('MeasureName', '')
  15. for MeasureName in AllMeasureNames:gmatch('[^%|]+') do
  16. table.insert(Feeds, {
  17. Measure = SKIN:GetMeasure(MeasureName),
  18. MeasureName = MeasureName,
  19. Raw = nil,
  20. Type = nil,
  21. Title = nil,
  22. Link = nil,
  23. Error = nil
  24. })
  25. end
  26.  
  27. -- MODULES
  28. EventFile_Initialize()
  29. HistoryFile_Initialize()
  30.  
  31. -- SET STARTING FEED
  32. f = f or 1
  33.  
  34. -- SET USER INPUT
  35. UserInput = false
  36. -- Used to detect when an item has been marked as read.
  37. end
  38.  
  39. function Update()
  40. Input()
  41. return Output()
  42. end
  43.  
  44. -----------------------------------------------------------------------
  45. -- INPUT
  46.  
  47. function compareTime(a,b)
  48. return a.Date < b.Date
  49. end
  50.  
  51. function Input(a)
  52. local f = a or f
  53.  
  54. local Raw = Feeds[f].Measure:GetStringValue()
  55.  
  56. if Raw == '' then
  57. Feeds[f].Error = {
  58. Description = 'Waiting for data from WebParser.',
  59. Title = 'Loading...',
  60. Link = 'http://enigma.kaelri.com/support'
  61. }
  62. return false
  63. elseif (Raw ~= Feeds[f].Raw) or UserInput then
  64. Feeds[f].Raw = Raw
  65.  
  66. -- DETERMINE FEED FORMAT AND CONTENTS
  67. local t = IdentifyType(Raw)
  68.  
  69. if not t then
  70. Feeds[f].Error = {
  71. Description = 'Could not identify a valid feed format.',
  72. Title = 'Invalid Feed Format',
  73. Link = 'http://enigma.kaelri.com/support'
  74. }
  75. return false
  76. else
  77. Feeds[f].Type = t
  78. end
  79.  
  80. -- MAKE SYNTAX PRETTIER
  81. local Type = Types[t]
  82.  
  83. -- GET NEW DATA
  84.  
  85. if (t == 'GoogleCalendar') then
  86.  
  87. Feeds[f].Title = Raw:match('X%-WR%-CALNAME:(.-)\n') or 'Untitled'
  88.  
  89. else
  90.  
  91. Feeds[f].Title = Raw:match('<title.->(.-)</title>') or 'Untitled'
  92.  
  93. end
  94.  
  95. Feeds[f].Link = Raw:match(Type.MatchLink) or nil
  96.  
  97. local Items = {}
  98.  
  99. -- MATCH RAW DATA
  100. Item.Unread = 1
  101.  
  102. if (t == 'GoogleCalendar') then
  103. Item.Title = RawItem:match('SUMMARY:(.-)\n') or nil
  104. if (string.len(Item.Title) == 1) then
  105. Item.Title = RawItem:match('DESCRIPTION:(.-)\n') or nil
  106. end
  107. Item.Title = Item.Title:gsub('\\', '')
  108. else
  109. Item.Title = RawItem:match('<title.->(.-)</title>') or nil
  110. end
  111.  
  112. Item.Date = RawItem:match(Type.MatchItemDate) or nil
  113. Item.Link = RawItem:match(Type.MatchItemLink) or nil
  114. Item.Desc = RawItem:match(Type.MatchItemDesc) or nil
  115.  
  116. Item.ID = RawItem:match(Type.MatchItemID) or Item.Link or Item.Title or Item.Desc or Item.Date
  117.  
  118. -- ADDITIONAL PARSING
  119. if (not Item.Title) or (Item.Title == '') then
  120. Item.Title = 'Untitled'
  121. end
  122. if Item.Desc then
  123. Item.Desc = Item.Desc:gsub('<.->', '')
  124. Item.Desc = Item.Desc:gsub('%s%s+', ' ')
  125. end
  126. Item.Date, Item.AllDay, Item.RealDate = IdentifyDate(Item.Date, t)
  127.  
  128. table.insert(Items, Item)
  129. end
  130.  
  131. table.sort(Items, compareTime)
  132.  
  133. -- IDENTIFY DUPLICATES
  134. for i, OldItem in ipairs(Feeds[f]) do
  135. for j, NewItem in ipairs(Items) do
  136. if NewItem.ID == OldItem.ID then
  137. Feeds[f][i].Match = j
  138. Items[j].Unread = OldItem.Unread
  139. if NewItem.RealDate == 0 then
  140. Items[j].Date = OldItem.Date
  141. Items[j].AllDay = OldItem.AllDay
  142. end
  143. end
  144. end
  145. end
  146.  
  147. -- CLEAR DUPLICATES OR ALL HISTORY
  148. local KeepOldItems = SELF:GetNumberOption('KeepOldItems', 0)
  149.  
  150. if (KeepOldItems == 1) and Type.MergeItems then
  151. for i = #Feeds[f], 1, -1 do
  152. if Feeds[f][i].Match then
  153. table.remove(Feeds[f], i)
  154. end
  155. end
  156. else
  157. for i = 1, #Feeds[f] do
  158. table.remove(Feeds[f])
  159. end
  160. end
  161.  
  162. -- ADD NEW ITEMS
  163. for i = #Items, 1, -1 do
  164. if Items[i] then
  165. if t == 'GoogleCalendar' then
  166. if (Items[i].Date > os.time()) then
  167. table.insert(Feeds[f], 1, Items[i])
  168. end
  169. else
  170. table.insert(Feeds[f], 1, Items[i])
  171. end
  172. end
  173. end
  174.  
  175. -- CHECK NUMBER OF ITEMS
  176. local MaxItems = SELF:GetNumberOption('MaxItems', nil)
  177. local MaxItems = (MaxItems > 0) and MaxItems or nil
  178.  
  179. if #Feeds[f] == 0 then
  180. Feeds[f].Error = {
  181. Description = 'No items found.',
  182. Title = Feeds[f]['Title'],
  183. Link = Feeds[f]['Link']
  184. }
  185. return false
  186. elseif MaxItems and (#Feeds[f] > MaxItems) then
  187. for i = #Feeds[f], (MaxItems + 1), -1 do
  188. table.remove(Feeds[f])
  189. end
  190. end
  191.  
  192. -- MODULES
  193. EventFile_Update(f)
  194. HistoryFile_Update(f)
  195.  
  196. -- CLEAR ERRORS FROM PREVIOUS UPDATE
  197. Feeds[f].Error = nil
  198.  
  199. -- RESET USER INPUT
  200. UserInput = false
  201. end
  202.  
  203. return true
  204. end
  205.  
  206. -----------------------------------------------------------------------
  207. -- OUTPUT
  208.  
  209. function Output()
  210. local Queue = {}
  211.  
  212. -- MAKE SYNTAX PRETTIER
  213. local Feed = Feeds[f]
  214. local Type = Types[Feed.Type]
  215. local Error = Feed.Error
  216.  
  217. -- BUILD QUEUE
  218. Queue['CurrentFeed'] = f
  219. Queue['NumberOfItems'] = #Feed
  220.  
  221. -- CHECK FOR INPUT ERRORS
  222. local MinItems = SELF:GetNumberOption('MinItems', 0)
  223. local Timestamp = SELF:GetOption('Timestamp', '%I.%M %p on %d %B %Y')
  224.  
  225. if Error then
  226. -- ERROR; QUEUE MESSAGES
  227. Queue['FeedTitle'] = Error.Title
  228. Queue['FeedLink'] = Error.Link
  229. Queue['Item1Title'] = Error.Description
  230. Queue['Item1Link'] = Error.Link
  231. Queue['Item1Desc'] = ''
  232. Queue['Item1Date'] = ''
  233. Queue['Item1Unread'] = 0
  234.  
  235. for i = 2, MinItems do
  236. Queue['Item'..i..'Title'] = ''
  237. Queue['Item'..i..'Link'] = ''
  238. Queue['Item'..i..'Desc'] = ''
  239. Queue['Item'..i..'Date'] = ''
  240. Queue['Item'..i..'Unread'] = 0
  241. end
  242. else
  243. -- NO ERROR; QUEUE FEED
  244. Queue['FeedTitle'] = Feed.Title
  245. Queue['FeedLink'] = Feed.Link or ''
  246.  
  247. for i = 1, math.max(#Feed, MinItems) do
  248. local Item = Feed[i] or {}
  249. Queue['Item'..i..'Title'] = Item.Title or ''
  250. Queue['Item'..i..'Link'] = Item.Link or Feed.Link or ''
  251. Queue['Item'..i..'Desc'] = Item.Desc or ''
  252. Queue['Item'..i..'Unread'] = Item.Unread or ''
  253. Queue['Item'..i..'Date'] = Item.Date and os.date(Timestamp, Item.Date) or ''
  254. end
  255. end
  256.  
  257. -- SET VARIABLES
  258. local VariablePrefix = SELF:GetOption('VariablePrefix', '')
  259. for k, v in pairs(Queue) do
  260. SKIN:Bang('!SetVariable', VariablePrefix..k, v)
  261. end
  262.  
  263. -- FINISH ACTION
  264. local FinishAction = SELF:GetOption('FinishAction', '')
  265. if FinishAction ~= '' then
  266. SKIN:Bang(FinishAction)
  267. end
  268.  
  269. return Error and Error.Description or 'Finished #'..f..' ('..Feed.MeasureName..'). Name: '..Feed.Title..'. Type: '..Feed.Type..'. Items: '..#Feed..'.'
  270. end
  271.  
  272. -----------------------------------------------------------------------
  273. -- EXTERNAL COMMANDS
  274.  
  275. function Refresh(a)
  276. a = a and tonumber(a) or f
  277. if a == f then
  278. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  279. else
  280. Input(a)
  281. end
  282. end
  283.  
  284. function Show(a)
  285. f = tonumber(a)
  286. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  287. end
  288.  
  289. function ShowNext()
  290. f = (f % #Feeds) + 1
  291. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  292. end
  293.  
  294. function ShowPrevious()
  295. f = (f == 1) and #Feeds or (f - 1)
  296. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  297. end
  298.  
  299. function MarkRead(a, b)
  300. b = b and tonumber(b) or f
  301. Feeds[b][a].Unread = 0
  302. UserInput = true
  303. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  304. end
  305.  
  306. function MarkUnread(a, b)
  307. b = b and tonumber(b) or f
  308. Feeds[b][a].Unread = 1
  309. UserInput = true
  310. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  311. end
  312.  
  313. function ToggleUnread(a, b)
  314. b = b and tonumber(b) or f
  315. Feeds[b][a].Unread = 1 - Feeds[b][a].Unread
  316. UserInput = true
  317. SKIN:Bang('!UpdateMeasure', SELF:GetName())
  318. end
  319.  
  320. -----------------------------------------------------------------------
  321. -- TYPES
  322.  
  323. function DefineTypes()
  324. Types = {
  325. RSS = {
  326. MatchLink = '<link.->(.-)</link>',
  327. MatchItem = '<item.-</item>',
  328. MatchItemID = '<guid.->(.-)</guid>',
  329. MatchItemLink = '<link.->(.-)</link>',
  330. MatchItemDesc = '<description.->(.-)</description>',
  331. MatchItemDate = '<pubDate.->(.-)</pubDate>',
  332. MergeItems = true,
  333. ParseDate = function(s)
  334. local Date = {}
  335. local MatchTime = '%a%a%a, (%d%d) (%a%a%a) (%d%d%d%d) (%d%d)%:(%d%d)%:(%d%d) (.-)$'
  336. local MatchDate = '%a%a%a, (%d%d) (%a%a%a) (%d%d%d%d)$'
  337. if s:match(MatchTime) then
  338. Date.day, Date.month, Date.year, Date.hour, Date.min, Date.sec, Date.Offset = s:match(MatchTime)
  339. elseif s:match(MatchDate) then
  340. Date.day, Date.month, Date.year = s:match(MatchDate)
  341. end
  342. return (Date.year and Date.month and Date.day) and Date or nil
  343. end
  344. },
  345. Atom = {
  346. MatchLink = '<link.-href=["\'](.-)["\']',
  347. MatchItem = '<entry.-</entry>',
  348. MatchItemID = '<id.->(.-)</id>',
  349. MatchItemLink = '<link.-href=["\'](.-)["\']',
  350. MatchItemDesc = '<summary.->(.-)</summary>',
  351. MatchItemDate = '<modified.->(.-)</modified>',
  352. MergeItems = true,
  353. ParseDate = function(s)
  354. local Date = {}
  355. local MatchTime = '(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d)%:(%d%d)%:(%d%d)(.-)$'
  356. local MatchDate = '(%d%d%d%d)%-(%d%d)%-(%d%d)$'
  357. if s:match(MatchTime) then
  358. Date.year, Date.month, Date.day, Date.hour, Date.min, Date.sec, Date.Offset = s:match(MatchTime)
  359. elseif s:match(MatchDate) then
  360. Date.year, Date.month, Date.day = s:match(MatchDate)
  361. end
  362. return Date
  363. end
  364. },
  365. GoogleCalendar = {
  366. MatchLink = 'UID:(%S+)',
  367. MatchItem = 'BEGIN:VEVENT.-END:VEVENT',
  368. MatchItemID = 'UID:(%S+)',
  369. MatchItemLink = 'UID:(%S+)',
  370. MatchItemDesc = 'DESCRIPTION:(%S+)',
  371. --MatchItemDate = 'DTSTART.-DTSTAMP',
  372. MatchItemDate = 'DTSTART.-UID',
  373. MergeItems = false,
  374. ParseDate = function(s)
  375. local Date = {}
  376.  
  377. -- For finding the Offset
  378. local DS = {}
  379. local StampMatch = 'DTSTAMP:(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)(%S+)'
  380. if s:match(StampMatch) then
  381. DS.year, DS.month, DS.day, DS.hour, DS.min, DS.sec, DS.Offset = s:match(StampMatch)
  382. -- Remove date from main string so it doesn't interfere with date matching below
  383. s = s:gsub('DTSTAMP:' .. DS.year .. DS.month .. DS.day .. 'T' .. DS.hour .. DS.min .. DS.sec .. DS.Offset, '')
  384. end
  385.  
  386. local MatchUntil = 'UNTIL=(%d%d%d%d)(%d%d)(%d%d)'
  387. if s:match(MatchUntil) then
  388.  
  389. Date.year, Date.month, Date.day = s:match('(%d%d%d%d)(%d%d)(%d%d)')
  390. return Date
  391.  
  392. -- Monthly recurring events
  393. elseif s:match('BYMONTHDAY=(%S+)') then
  394. Date.year = os.date('%Y')
  395. Date.month = os.date('%m')
  396. Date.day = s:match('BYMONTHDAY=(%S+)')
  397.  
  398. -- If date is in the past then add a month
  399. if os.time(Date) < os.time() then
  400. if tonumber(Date.month) < 12 then
  401. Date.month = Date.month + 1
  402. else
  403. Date.month = 1
  404. Date.year = Date.year + 1
  405. end
  406. end
  407.  
  408. return Date
  409.  
  410. -- Yearly recurring events
  411. elseif s:match('FREQ=YEARLY') then
  412. -- Match and set to this year
  413. Date.year, Date.month, Date.day = s:match('DTSTART;VALUE=DATE:(%d%d%d%d)(%d%d)(%d%d)')
  414. Date.year = os.date('%Y')
  415.  
  416. return Date
  417.  
  418. -- Day is not numeric, e.g. '1FR' for first Friday of month or '2TH' for 2nd Thursday
  419. elseif s:match('BYDAY=(%d+)(%u+)') then
  420.  
  421. local days1 = {['Mon']=0, ['Tue']=1, ['Wed']=2, ['Thu']=3, ['Fri']=4, ['Sat']=5, ['Sun']=6}
  422. local days2 = {['MO']=0, ['TU']=1, ['WE']=2, ['TH']=3, ['FR']=4, ['SA']=5, ['SU']=6}
  423. -- Get first day of current month
  424. local d = os.date('%a', os.time{ year=os.date('%Y'), month=os.date('%m'), day=1 })
  425.  
  426. local wday = {}
  427. wday.n, wday.d = s:match('BYDAY=(%d+)(%u+)')
  428.  
  429. local daydiff = (days2[wday.d] - days1[d]) + 1
  430. if daydiff < 0 then
  431. daydiff = daydiff + 7
  432. end
  433.  
  434. -- nth of day
  435. daydiff = daydiff + ((wday.n - 1) * 7)
  436.  
  437. Date.year = os.date('%Y')
  438. Date.month = os.date('%m')
  439. Date.day = daydiff
  440.  
  441. -- If date is in the past then add a month
  442. if os.time(Date) < os.time() then
  443. if tonumber(Date.month) < 12 then
  444. Date.month = Date.month + 1
  445. else
  446. Date.month = 1
  447. Date.year = Date.year + 1
  448. end
  449. end
  450.  
  451. -- Match time from original record
  452. local Date2 = {}
  453. if s:match('(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)') then
  454. Date2.year, Date2.month, Date2.day, Date2.hour, Date2.min, Date2.sec = s:match('(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)')
  455. Date.hour = Date2.hour
  456. Date.min = Date2.min
  457. Date.sec = Date2.sec
  458. Date.Offset = DS.Offset
  459. end
  460.  
  461. return Date
  462. end
  463.  
  464. -- Standard date fallback
  465. local MatchTime = '(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)'
  466. local MatchDate = '(%d%d%d%d)(%d%d)(%d%d)'
  467.  
  468. if s:match(MatchTime) then
  469. Date.year, Date.month, Date.day, Date.hour, Date.min, Date.sec = s:match(MatchTime)
  470. Date.Offset = DS.Offset
  471. else
  472. Date.year, Date.month, Date.day = s:match(MatchDate)
  473. end
  474.  
  475. return Date
  476. end
  477. },
  478. RememberTheMilk = {
  479. MatchLink = '<link.-rel=.-alternate.-href=["\'](.-)["\']',
  480. MatchItem = '<entry.-</entry>',
  481. MatchItemID = '<id.->(.-)</id>',
  482. MatchItemLink = '<link.-href=["\'](.-)["\']',
  483. MatchItemDesc = '<summary.->(.-)</summary>',
  484. MatchItemDate = '<span class=["\']rtm_due_value["\']>(.-)</span>',
  485. MergeItems = false,
  486. ParseDate = function(s)
  487. local Date = {}
  488. local MatchTime = '%a%a%a (%d+) (%a%a%a) (%d+) at (%d+)%:(%d+)(%a%a)' -- e.g. 'Wed 7 Nov 12 at 3:17PM'
  489. local MatchDate = '%a%a%a (%d+) (%a%a%a) (%d+)' -- e.g. 'Tue 25 Dec 12'
  490. if s:match(MatchTime) then
  491. Date.day, Date.month, Date.year, Date.hour, Date.min, Date.Meridiem = s:match(MatchTime)
  492. elseif s:match(MatchDate) then
  493. Date.day, Date.month, Date.year = s:match(MatchDate)
  494. end
  495. return Date
  496. end
  497. }
  498. }
  499. end
  500.  
  501. -------------------------
  502.  
  503. function IdentifyType(s)
  504.  
  505. -- COLLAPSE CONTAINER TAGS
  506. for _, v in ipairs{ 'item', 'entry' } do
  507. s = s:gsub('<'..v..'.->.+</'..v..'>', '<'..v..'></'..v..'>') -- e.g. '<entry.->.+</entry>' --> '<entry></entry>'
  508. end
  509.  
  510. --DEFINE RSS MARKER TESTS
  511. --Each of these test functions will be run in turn, until one of them gets a solid match on the format type.
  512. local TestRSS = {
  513. function(a)
  514. -- If the feed contains these tags outside of <item> or <entry>, RSS is confirmed.
  515. for _, v in ipairs{ '<rss', '<channel', '<lastBuildDate', '<pubDate', '<ttl', '<description' } do
  516. if a:match(v) then
  517. return 'RSS'
  518. end
  519. end
  520. return false
  521. end,
  522.  
  523. function(a)
  524. -- Alternatively, if the feed contains these tags outside of <item> or <entry>, Atom is confirmed.
  525. for _, v in ipairs{ '<feed', '<subtitle' } do
  526. if a:match(v) then
  527. return 'Atom'
  528. end
  529. end
  530. return false
  531. end,
  532.  
  533. function(a)
  534. -- Alternatively, if the feed contains these tags ICAL is confirmed.
  535. for _, v in ipairs{ 'DTSTART', 'DTEND' } do
  536. if a:match(v) then
  537. return 'Ical'
  538. end
  539. end
  540. return false
  541. end,
  542.  
  543. function(a)
  544. -- If no markers are present, we search for <item> or <entry> tags to confirm the type.
  545. local HaveItems = a:match('<item')
  546. local HaveEntries = a:match('<entry')
  547. local HaveIcal = a:match('DTSTART')
  548.  
  549. if HaveItems and not HaveEntries then
  550. return 'RSS'
  551. elseif HaveEntries and not HaveItems then
  552. return 'Atom'
  553. elseif HaveIcal then
  554. return 'Ical'
  555. else
  556. -- If both kinds of tags are present, and no markers are given, then I give up
  557. -- because your feed is ridiculous. And if neither tag is present, then no type
  558. -- can be confirmed (and there would be no usable data anyway).
  559. return false
  560. end
  561. end
  562. }
  563.  
  564. -- RUN RSS MARKER TESTS
  565. local Class = false
  566. for _, v in ipairs(TestRSS) do
  567. Class = v(s)
  568. if Class then break end
  569. end
  570.  
  571. -- DETECT SUBTYPE AND RETURN
  572. if Class == 'RSS' then
  573. return 'RSS'
  574. elseif Class == 'Atom' then
  575. if s:match('xmlns:gCal') then
  576. return 'GoogleCalendar'
  577. elseif s:match('<subtitle>rememberthemilk.com</subtitle>') then
  578. return 'RememberTheMilk'
  579. else
  580. return 'Atom'
  581. end
  582. elseif Class == 'Ical' then
  583. return 'GoogleCalendar'
  584. else
  585. return false
  586. end
  587. end
  588.  
  589. -------------------------
  590.  
  591. function IdentifyDate(s, t)
  592.  
  593. local Date = nil
  594.  
  595. Date = s and Types[t].ParseDate(s) or {}
  596.  
  597. Date.year = tonumber(Date.year) or nil
  598. Date.month = tonumber(Date.month) or MonthAcronyms[Date.month] or nil
  599. Date.day = tonumber(Date.day) or nil
  600. Date.hour = tonumber(Date.hour) or nil
  601. Date.min = tonumber(Date.min) or nil
  602. Date.sec = tonumber(Date.sec) or 0
  603.  
  604. -- FIND ENOUGH ELEMENTS, OR DEFAULT TO RETRIEVAL DATE
  605. local RealDate, AllDay
  606.  
  607. if (Date.year and Date.month and Date.day) then
  608. RealDate = 1
  609.  
  610. -- DETECT ALL-DAY EVENT
  611. if (Date.hour and Date.min) then
  612. AllDay = 0
  613. else
  614. AllDay = 1
  615. Date.hour = 0
  616. Date.min = 0
  617. end
  618.  
  619. -- GET CURRENT LOCAL TIME, UTC OFFSET
  620. -- These values are referenced in several procedures below.
  621. local UTC = os.date('!*t')
  622. local LocalTime = os.date('*t')
  623. local DaylightSavings = LocalTime.isdst and 3600 or 0
  624. local LocalOffset = os.time(LocalTime) - os.time(UTC) + DaylightSavings
  625.  
  626. -- CHANGE 12-HOUR to 24-HOUR
  627. if Date.Meridiem then
  628. if (Date.Meridiem == 'AM') and (Date.hour == 12) then
  629. Date.hour = 0
  630. elseif (Date.Meridiem == 'PM') and (Date.hour < 12) then
  631. Date.hour = Date.hour + 12
  632. end
  633. end
  634.  
  635. -- FIND CLOSEST MATCH FOR TWO-DIGIT YEAR
  636. if Date.year < 100 then
  637. local CurrentYear = LocalTime.year
  638. local CurrentCentury = math.floor(CurrentYear / 100) * 100
  639. local IfThisCentury = CurrentCentury + Date.year
  640. local IfNextCentury = CurrentCentury + Date.year + 100
  641. if math.abs(CurrentYear - IfThisCentury) < math.abs(CurrentYear - IfNextCentury) then
  642. Date.year = IfThisCentury
  643. else
  644. Date.year = IfNextCentury
  645. end
  646. end
  647.  
  648.  
  649.  
  650. -- GET INPUT OFFSET FROM UTC (OR DEFAULT TO LOCAL)
  651. if (Date.Offset) and (Date.Offset ~= '') then
  652. if Date.Offset:match('%a') then
  653. Date.Offset = TimeZones[Date.Offset] and (TimeZones[Date.Offset] * 3600) or 0
  654. elseif Date.Offset:match('%d') then
  655. local Direction, Hours, Minutes = Date.Offset:match('^([^%d]-)(%d+)[^%d]-(%d%d)')
  656.  
  657. Direction = Direction:match('%-') and -1 or 1
  658. Hours = tonumber(Hours) * 3600
  659. Minutes = tonumber(Minutes) and (tonumber(Minutes) * 60) or 0
  660.  
  661. Date.Offset = (Hours + Minutes) * Direction
  662. end
  663. else
  664. Date.Offset = LocalOffset
  665. end
  666.  
  667. -- RETURN CONVERTED DATE
  668. Date = os.time(Date) + LocalOffset - Date.Offset
  669. else
  670. -- NO USABLE DATE FOUND; USE RETRIEVAL DATE INSTEAD
  671. RealDate = 0
  672. AllDay = 0
  673. Date = os.time()
  674. end
  675.  
  676. return Date, AllDay, RealDate
  677. end
  678.  
  679. -----------------------------------------------------------------------
  680. -- EVENT FILE MODULE
  681.  
  682. function EventFile_Initialize()
  683. local EventFiles = {}
  684. local AllEventFiles = SELF:GetOption('EventFile', '')
  685. for EventFile in AllEventFiles:gmatch('[^%|]+') do
  686. table.insert(EventFiles, EventFile)
  687. end
  688. for i, v in ipairs(Feeds) do
  689. local EventFile = EventFiles[i] or SELF:GetName()..'_Feed'..i..'Events.xml'
  690. Feeds[i].EventFile = SKIN:MakePathAbsolute(EventFile)
  691. end
  692. end
  693.  
  694. function EventFile_Update(a)
  695. local f = a or f
  696.  
  697. local WriteEvents = SELF:GetNumberOption('WriteEvents', 0)
  698. if (WriteEvents == 1) and (Feeds[f].Type == 'GoogleCalendar') then
  699. -- CREATE XML TABLE
  700. local WriteLines = {}
  701. table.insert(WriteLines, '<EventFile Title="'..Feeds[f].Title..'">')
  702. for i, v in ipairs(Feeds[f]) do
  703. local ItemDate = os.date('*t', v.Date)
  704. table.insert(WriteLines, '<Event Month="'..ItemDate['month']..'" Day="'..ItemDate['day']..'" Desc="'..v.Title..'"/>')
  705. end
  706. table.insert(WriteLines, '</EventFile>')
  707.  
  708. -- WRITE FILE
  709. local WriteFile = io.output(Feeds[f].EventFile, 'w')
  710. if WriteFile then
  711. local WriteContent = table.concat(WriteLines, '\r\n')
  712. WriteFile:write(WriteContent)
  713. WriteFile:close()
  714. else
  715. SKIN:Bang('!Log', SELF:GetName()..': cannot open file: '..Feeds[f].EventFile)
  716. end
  717. end
  718. end
  719.  
  720. -----------------------------------------------------------------------
  721. -- HISTORY FILE MODULE
  722.  
  723. function HistoryFile_Initialize()
  724. -- DETERMINE FILEPATH
  725. HistoryFile = SELF:GetOption('HistoryFile', SELF:GetName()..'History.xml')
  726. HistoryFile = SKIN:MakePathAbsolute(HistoryFile)
  727.  
  728. -- CREATE HISTORY DATABASE
  729. History = {}
  730.  
  731. -- CHECK IF FILE EXISTS
  732. local ReadFile = io.open(HistoryFile)
  733. if ReadFile then
  734. local ReadContent = ReadFile:read('*all')
  735. ReadFile:close()
  736.  
  737. -- PARSE HISTORY FROM LAST SESSION
  738. for ReadFeedURL, ReadFeed in ReadContent:gmatch('<feed URL=(%b"")>(.-)</feed>') do
  739. local ReadFeedURL = ReadFeedURL:match('^"(.-)"$')
  740. History[ReadFeedURL] = {}
  741. for ReadItem in ReadFeed:gmatch('<item>(.-)</item>') do
  742. local Item = {}
  743. for Key, Value in ReadItem:gmatch('<(.-)>(.-)</.->') do
  744. Value = Value:gsub('&lt;', '<')
  745. Value = Value:gsub('&gt;', '>')
  746. Item[Key] = Value
  747. end
  748. Item.Date = tonumber(Item.Date) or Item.Date
  749. Item.Unread = tonumber(Item.Unread)
  750. table.insert(History[ReadFeedURL], Item)
  751. end
  752. end
  753. end
  754.  
  755. -- ADD HISTORY TO MAIN DATABASE
  756. -- For each feed, if URLs match, add all contents from History[h] to Feeds[f].
  757. for f, Feed in ipairs(Feeds) do
  758. local h = Feed.Measure:GetOption('URL')
  759. Feeds[f].URL = h
  760. if History[h] then
  761. for _, Item in ipairs(History[h]) do
  762. table.insert(Feeds[f], Item)
  763. end
  764. end
  765. end
  766. end
  767.  
  768. function HistoryFile_Update(a)
  769. local f = a or f
  770.  
  771. -- CLEAR AND REBUILD HISTORY
  772. local h = Feeds[f].URL
  773. History[h] = {}
  774. for i, Item in ipairs(Feeds[f]) do
  775. table.insert(History[h], Item)
  776. end
  777.  
  778. -- WRITE HISTORY IF REQUESTED
  779. WriteHistory()
  780. end
  781.  
  782. function WriteHistory()
  783. local WriteHistory = SELF:GetNumberOption('WriteHistory', 0)
  784. if WriteHistory == 1 then
  785. -- GENERATE XML TABLE
  786. local WriteLines = {}
  787. for WriteURL, WriteFeed in pairs(History) do
  788. table.insert(WriteLines, string.format( '<feed URL=%q>', WriteURL))
  789. for _, WriteItem in ipairs(WriteFeed) do
  790. table.insert(WriteLines, '\t<item>')
  791. for Key, Value in pairs(WriteItem) do
  792. Value = string.gsub(Value, '<', '&lt;')
  793. Value = string.gsub(Value, '>', '&gt;')
  794. table.insert(WriteLines, string.format( '\t\t<%s>%s</%s>', Key, Value, Key))
  795. end
  796. table.insert(WriteLines, '\t</item>')
  797. end
  798. table.insert(WriteLines, '</feed>')
  799. end
  800.  
  801. -- WRITE XML TO FILE
  802. local WriteFile = io.open(HistoryFile, 'w')
  803. if WriteFile then
  804. local WriteContent = table.concat(WriteLines, '\n')
  805. WriteFile:write(WriteContent)
  806. WriteFile:close()
  807. else
  808. SKIN:Bang('!Log', SELF:GetName()..': cannot open file: '..HistoryFile)
  809. end
  810. end
  811. end
  812.  
  813. function ClearHistory()
  814. local DeleteFile = io.open(HistoryFile)
  815. if DeleteFile then
  816. DeleteFile:close()
  817. os.remove(HistoryFile)
  818. SKIN:Bang('!Log', SELF:GetName()..': deleted history cache at '..HistoryFile)
  819. end
  820. SKIN:Bang('!Refresh')
  821. end
  822.  
  823. -----------------------------------------------------------------------
  824. -- CONSTANTS
  825.  
  826. TimeZones = {
  827. IDLW = -12, -- International Date Line West
  828. NT = -11, -- Nome
  829. CAT = -10, -- Central Alaska
  830. HST = -10, -- Hawaii Standard
  831. HDT = -9, -- Hawaii Daylight
  832. YST = -9, -- Yukon Standard
  833. YDT = -8, -- Yukon Daylight
  834. PST = -8, -- Pacific Standard
  835. PDT = -7, -- Pacific Daylight
  836. MST = -7, -- Mountain Standard
  837. MDT = -6, -- Mountain Daylight
  838. CST = -6, -- Central Standard
  839. CDT = -5, -- Central Daylight
  840. EST = -5, -- Eastern Standard
  841. EDT = -4, -- Eastern Daylight
  842. AST = -3, -- Atlantic Standard
  843. ADT = -2, -- Atlantic Daylight
  844. WAT = -1, -- West Africa
  845. GMT = 0, -- Greenwich Mean
  846. UTC = 0, -- Universal (Coordinated)
  847. Z = 0, -- Zulu, alias for UTC
  848. WET = 0, -- Western European
  849. BST = 1, -- British Summer
  850. CET = 1, -- Central European
  851. MET = 1, -- Middle European
  852. MEWT = 1, -- Middle European Winter
  853. MEST = 2, -- Middle European Summer
  854. CEST = 2, -- Central European Summer
  855. MESZ = 2, -- Middle European Summer
  856. FWT = 1, -- French Winter
  857. FST = 2, -- French Summer
  858. EET = 2, -- Eastern Europe, USSR Zone 1
  859. EEST = 3, -- Eastern European Daylight
  860. WAST = 7, -- West Australian Standard
  861. WADT = 8, -- West Australian Daylight
  862. CCT = 8, -- China Coast, USSR Zone 7
  863. JST = 9, -- Japan Standard, USSR Zone 8
  864. EAST = 10, -- Eastern Australian Standard
  865. EADT = 11, -- Eastern Australian Daylight
  866. GST = 10, -- Guam Standard, USSR Zone 9
  867. NZT = 12, -- New Zealand
  868. NZST = 12, -- New Zealand Standard
  869. NZDT = 13, -- New Zealand Daylight
  870. IDLE = 12 -- International Date Line East
  871. }
  872.  
  873. MonthAcronyms = {
  874. Jan = 1,
  875. Feb = 2,
  876. Mar = 3,
  877. Apr = 4,
  878. May = 5,
  879. Jun = 6,
  880. Jul = 7,
  881. Aug = 8,
  882. Sep = 9,
  883. Oct = 10,
  884. Nov = 11,
  885. Dec = 12
  886. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement