Advertisement
Guest User

Rainmeter Enigma Fixes

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