I forgot to tell that the parameter %y must be added also in Timestamp in Reader.lua, now I have to correct daylight savings time!
Thank you again
I forgot to tell that the parameter %y must be added also in Timestamp in Reader.lua, now I have to correct daylight savings time!
Glad to help.Nicknightfly wrote: ↑October 26th, 2022, 8:53 am I forgot to tell that the parameter %y must be added also in Timestamp in Reader.lua, now I have to correct daylight savings time!
eclectic-tech wrote: ↑January 11th, 2018, 4:04 pm Hmm, not sure what you did differently.
If you are able to pull other calendars, I would look for what is different with your URL versus a working one.
Double check the secret address for your calendar in the [Variables] section, it should be in this form:
GoogleCalendar3=https://calendar.google.com/calendar/ical/[i]{youremailaddress}[/i]%40gmail.com/private-{numricCode}/basic.ics.
Also change tabs from 2 to 3:
NumberOfTabs=3
EDIT: I am still working on daily events display. This may be what you are missing. I will post an update when that is working (WIP for sure!)...
Not seeing that with my current Reader.lua script. That seems to be related to the Item.Title, if our scripts are similar...
Code: Select all
function Initialize()
-- SET UPDATE DIVIDER
SKIN:Bang('!SetOption', SELF:GetName(), 'UpdateDivider', -1)
-- This script never needs to update on a schedule. It should only
-- update when it gets a "Refresh" command from WebParser.
-- CREATE MAIN DATABASE
Feeds = {}
-- CREATE TYPE MATCHING PATTERNS AND FORMATTING FUNCTIONS
DefineTypes()
-- GET MEASURE NAMES
local AllMeasureNames = SELF:GetOption('MeasureName', '')
for MeasureName in AllMeasureNames:gmatch('[^%|]+') do
table.insert(Feeds, {
Measure = SKIN:GetMeasure(MeasureName),
MeasureName = MeasureName,
Raw = nil,
Type = nil,
Title = nil,
Link = nil,
Error = nil
})
end
-- MODULES
EventFile_Initialize()
HistoryFile_Initialize()
-- SET STARTING FEED
f = f or 1
-- SET USER INPUT
UserInput = false
-- Used to detect when an item has been marked as read.
end
function Update()
Input()
return Output()
end
-----------------------------------------------------------------------
-- INPUT
function compareTime(a,b)
return a.Date < b.Date
end
function Input(a)
local f = a or f
local Raw = Feeds[f].Measure:GetStringValue()
if Raw == '' then
Feeds[f].Error = {
Description = 'Processing...',
Title = 'Loading...',
Link = 'http://enigma.kaelri.com/support'
}
return false
elseif (Raw ~= Feeds[f].Raw) or UserInput then
Feeds[f].Raw = Raw
-- DETERMINE FEED FORMAT AND CONTENTS
local t = IdentifyType(Raw)
if not t then
Feeds[f].Error = {
Description = 'Could not identify a valid feed format.',
Title = 'Invalid Feed Format',
Link = 'http://enigma.kaelri.com/support'
}
return false
else
Feeds[f].Type = t
end
-- MAKE SYNTAX PRETTIER
local Type = Types[t]
-- GET NEW DATA
if (t == 'iCalendar') then
Feeds[f].Title = Raw:match('X%-WR%-CALNAME:(.-)\n') or 'UnTitled'
else
Feeds[f].Title = Raw:match('<Title.->(.-)</Title>') or 'UnTitled'
end
if (t == 'iCalendar') then
Feeds[f].Link = 'https://calendar.google.com/calendar/r/month/' or nil
else
Feeds[f].Link = Raw:match(Type.MatchLink) or nil
end
local Items = {}
for RawItem in Raw:gmatch(Type.MatchItem) do
local Item = {}
-- MATCH RAW DATA
Item.Unread = 1
if (t == 'iCalendar') then
Item.Title = RawItem:match('SUMMARY:(.-)\n') or nil
if (string.len(Item.Title) == 1) then
Item.Title = RawItem:match('DESCRIPTION:(.-)\n') or nil
end
Item.Title = Item.Title:gsub('\\', '')
else
Item.Title = RawItem:match('<Title.->(.-)</Title>') or nil
end
Item.Date = RawItem:match(Type.MatchItemDate) or nil
if (t == 'iCalendar') then
Item.Link = 'https://calendar.google.com/calendar/r/month/' or nil
else
Item.Link = RawItem:match(Type.MatchItemLink) or nil
end
Item.Desc = RawItem:match(Type.MatchItemDesc) or nil
Item.ID = RawItem:match(Type.MatchItemID) or Item.Link or Item.Title or Item.Desc or Item.Date
-- ADDITIONAL PARSING
if (not Item.Title) or (Item.Title == '') then
Item.Title = 'UnTitled'
end
if Item.Desc then
Item.Desc = Item.Desc:gsub('<.->', '')
Item.Desc = Item.Desc:gsub('%s%s+', ' ')
end
Item.Date, Item.AllDay, Item.RealDate = IdentifyDate(Item.Date, t)
Item.Time = Item.Date
table.insert(Items, Item)
end
table.sort(Items, compareTime)
-- IDENTIFY DUPLICATES
for i, OldItem in ipairs(Feeds[f]) do
for j, NewItem in ipairs(Items) do
if NewItem.ID == OldItem.ID then
Feeds[f][i].Match = j
Items[j].Unread = OldItem.Unread
if NewItem.RealDate == 0 then
Items[j].Date = OldItem.Date
Items[j].AllDay = OldItem.AllDay
end
end
end
end
-- CLEAR DUPLICATES OR ALL HISTORY
local KeepOldItems = SELF:GetNumberOption('KeepOldItems', 0)
if (KeepOldItems == 1) and Type.MergeItems then
for i = #Feeds[f], 1, -1 do
if Feeds[f][i].Match then
table.remove(Feeds[f], i)
end
end
else
for i = 1, #Feeds[f] do
table.remove(Feeds[f])
end
end
-- ADD NEW ITEMS
for i = #Items, 1, -1 do
if Items[i] then
if t == 'iCalendar' then
if (Items[i].Date > os.time()) then
table.insert(Feeds[f], 1, Items[i])
end
else
table.insert(Feeds[f], 1, Items[i])
end
end
end
-- CHECK NUMBER OF ITEMS
local MaxItems = SELF:GetNumberOption('MaxItems', nil)
local MaxItems = (MaxItems > 0) and MaxItems or nil
if #Feeds[f] == 0 then
Feeds[f].Error = {
Description = 'No items found.',
Title = Feeds[f]['Title'],
Link = Feeds[f]['Link']
}
return false
elseif MaxItems and (#Feeds[f] > MaxItems) then
for i = #Feeds[f], (MaxItems + 1), -1 do
table.remove(Feeds[f])
end
end
-- MODULES
EventFile_Update(f)
HistoryFile_Update(f)
-- CLEAR ERRORS FROM PREVIOUS UPDATE
Feeds[f].Error = nil
-- RESET USER INPUT
UserInput = false
end
return true
end
-----------------------------------------------------------------------
-- OUTPUT
function Output()
local Queue = {}
-- MAKE SYNTAX PRETTIER
local Feed = Feeds[f]
local Type = Types[Feed.Type]
local Error = Feed.Error
-- BUILD QUEUE
Queue['CurrentFeed'] = f
Queue['NumberOfItems'] = #Feed
-- CHECK FOR INPUT ERRORS
local MinItems = SELF:GetNumberOption('MinItems', 0)
--local Timestamp = SELF:GetOption('Timestamp', '%I:%M %p %A %b %d')
local Timestamp = SELF:GetOption('Timestamp', '%A %b %d')
if Error then
-- ERROR; QUEUE MESSAGES
Queue['FeedTitle'] = Error.Title
Queue['FeedLink'] = Error.Link
Queue['Item1Title'] = Error.Description
Queue['Item1Link'] = Error.Link
Queue['Item1Desc'] = ''
Queue['Item1Date'] = ''
Queue['Item1Time'] = ''
Queue['Item1Unread'] = 0
for i = 2, MinItems do
Queue['Item'..i..'Title'] = ''
Queue['Item'..i..'Link'] = ''
Queue['Item'..i..'Desc'] = ''
Queue['Item'..i..'Date'] = ''
Queue['Item'..i..'Time'] = ''
Queue['Item'..i..'Unread'] = 0
end
else
-- NO ERROR; QUEUE FEED
Queue['FeedTitle'] = Feed.Title
Queue['FeedLink'] = Feed.Link or ''
for i = 1, math.max(#Feed, MinItems) do
local Item = Feed[i] or {}
Queue['Item'..i..'Title'] = Item.Title or ''
Queue['Item'..i..'Link'] = Item.Link or Feed.Link or ''
Queue['Item'..i..'Desc'] = Item.Desc or ''
Queue['Item'..i..'Unread'] = Item.Unread or ''
Queue['Item'..i..'Date'] = Item.Date and os.date(Timestamp, Item.Date) or ''
Queue['Item'..i..'Time'] = Item.Date and os.date('%I:%M -', Item.Date) or ''
-- print(Item.Title..Item.Date)
end
end
-- SET VARIABLES
local VariablePrefix = SELF:GetOption('VariablePrefix', '')
for k, v in pairs(Queue) do
SKIN:Bang('!SetVariable', VariablePrefix..k, v)
end
-- FINISH ACTION
local FinishAction = SELF:GetOption('FinishAction', '')
if FinishAction ~= '' then
SKIN:Bang(FinishAction)
end
return Error and Error.Description or 'Finished #'..f..' ('..Feed.MeasureName..'). Name: '..Feed.Title..'. Type: '..Feed.Type..'. Items: '..#Feed..'.'
end
-----------------------------------------------------------------------
-- EXTERNAL COMMANDS
function Refresh(a)
a = a and tonumber(a) or f
if a == f then
SKIN:Bang('!UpdateMeasure', SELF:GetName())
else
Input(a)
end
end
function Show(a)
f = tonumber(a)
SKIN:Bang('!UpdateMeasure', SELF:GetName())
end
function ShowNext()
f = (f % #Feeds) + 1
SKIN:Bang('!UpdateMeasure', SELF:GetName())
end
function ShowPrevious()
f = (f == 1) and #Feeds or (f - 1)
SKIN:Bang('!UpdateMeasure', SELF:GetName())
end
function MarkRead(a, b)
b = b and tonumber(b) or f
Feeds[b][a].Unread = 0
UserInput = true
SKIN:Bang('!UpdateMeasure', SELF:GetName())
end
function MarkUnread(a, b)
b = b and tonumber(b) or f
Feeds[b][a].Unread = 1
UserInput = true
SKIN:Bang('!UpdateMeasure', SELF:GetName())
end
function ToggleUnread(a, b)
b = b and tonumber(b) or f
Feeds[b][a].Unread = 1 - Feeds[b][a].Unread
UserInput = true
SKIN:Bang('!UpdateMeasure', SELF:GetName())
end
-----------------------------------------------------------------------
-- TYPES
function DefineTypes()
Types = {
RSS = {
MatchLink = '<link.->(.-)</link>',
MatchItem = '<item.-</item>',
MatchItemID = '<guid.->(.-)</guid>',
MatchItemLink = '<link.->(.-)</link>',
MatchItemDesc = '<description.->(.-)</description>',
MatchItemDate = '<pubDate.->(.-)</pubDate>',
MergeItems = true,
ParseDate = function(s)
local Date = {}
local MatchTime = '%a%a%a, (%d%d) (%a%a%a) (%d%d%d%d) (%d%d)%:(%d%d)%:(%d%d) (.-)$'
local MatchDate = '%a%a%a, (%d%d) (%a%a%a) (%d%d%d%d)$'
if s:match(MatchTime) then
Date.day, Date.month, Date.year, Date.hour, Date.min, Date.sec, Date.Offset = s:match(MatchTime)
elseif s:match(MatchDate) then
Date.day, Date.month, Date.year = s:match(MatchDate)
end
return (Date.year and Date.month and Date.day) and Date or nil
end
},
Atom = {
MatchLink = '<link.-href=["\'](.-)["\']',
MatchItem = '<entry.-</entry>',
MatchItemID = '<id.->(.-)</id>',
MatchItemLink = '<link.-href=["\'](.-)["\']',
MatchItemDesc = '<summary.->(.-)</summary>',
MatchItemDate = '<modified.->(.-)</modified>',
MergeItems = true,
ParseDate = function(s)
local Date = {}
local MatchTime = '(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d)%:(%d%d)%:(%d%d)(.-)$'
local MatchDate = '(%d%d%d%d)%-(%d%d)%-(%d%d)$'
if s:match(MatchTime) then
Date.year, Date.month, Date.day, Date.hour, Date.min, Date.sec, Date.Offset = s:match(MatchTime)
elseif s:match(MatchDate) then
Date.year, Date.month, Date.day = s:match(MatchDate)
end
return Date
end
},
iCalendar = {
MatchLink = 'UID:(%S+)',
MatchItem = 'BEGIN:VEVENT.-END:VEVENT',
MatchItemID = 'UID:(%S+)',
MatchItemLink = 'UID:(%S+)',
MatchItemDesc = 'DESCRIPTION:(.-)\n',
MatchItemDate = 'DTSTART.-UID',
MergeItems = false,
ParseDate = function(s)
local Date = {}
-- For finding the Offset
local DS = {}
local StampMatch = 'DTSTAMP:(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)(%S+)'
if s:match(StampMatch) then
DS.year, DS.month, DS.day, DS.hour, DS.min, DS.sec, DS.Offset = s:match(StampMatch)
-- Remove date from main string so it doesn't interfere with date matching below
s = s:gsub('DTSTAMP:' .. DS.year .. DS.month .. DS.day .. 'T' .. DS.hour .. DS.min .. DS.sec .. DS.Offset, '')
end
local MatchRecurrence = 'RECURRENCE-ID;VALUE=DATE:(%d%d%d%d)(%d%d)(%d%d)'
if s:match(MatchRecurrence) then
Date.year, Date.month, Date.day = s:match(MatchRecurrence)
return Date
-- Monthly recurring events
elseif s:match('FREQ=MONTHLY') then
Date.year = os.date('%Y')
Date.month = os.date('%m')
Date.day = s:match('DTSTART;VALUE=DATE:%d%d%d%d%d%d(%d%d)')
-- If date is in the past then add a month
if os.time(Date) < os.time() then
if tonumber(Date.month) < 12 then
Date.month = Date.month + 1
else
Date.month = 1
Date.year = Date.year + 1
end
end
return Date
-- Yearly recurring events
elseif s:match('FREQ=YEARLY') then
-- Match and set to this year
Date.year, Date.month, Date.day = s:match('DTSTART;VALUE=DATE:(%d%d%d%d)(%d%d)(%d%d)')
Date.year = os.date('%Y')
-- If date is in the past then add a year
if os.time(Date) < os.time() then
Date.year = Date.year + 1
end
return Date
-- Day is not numeric, e.g. '1FR' for first Friday of month or '2TH' for 2nd Thursday
elseif s:match('BYDAY=(%d+)(%u+)') then
local days1 = {['Mon']=0, ['Tue']=1, ['Wed']=2, ['Thu']=3, ['Fri']=4, ['Sat']=5, ['Sun']=6}
local days2 = {['MO']=0, ['TU']=1, ['WE']=2, ['TH']=3, ['FR']=4, ['SA']=5, ['SU']=6}
-- Get first day of current month
local d = os.date('%a', os.time{ year=os.date('%Y'), month=os.date('%m'), day=1 })
local wday = {}
wday.n, wday.d = s:match('BYDAY=(%d+)(%u+)')
local daydiff = (days2[wday.d] - days1[d]) + 1
if daydiff < 0 then
daydiff = daydiff + 7
end
-- nth of day
daydiff = daydiff + ((wday.n - 1) * 7)
Date.year = os.date('%Y')
Date.month = os.date('%m')
Date.day = daydiff
-- If date is in the past then add a month
if os.time(Date) < os.time() then
if tonumber(Date.month) < 12 then
Date.month = Date.month + 1
else
Date.month = 1
Date.year = Date.year + 1
end
end
-- Match time from original record
local Date2 = {}
if s:match('(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)') then
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)')
Date.hour = Date2.hour
Date.min = Date2.min
Date.sec = Date2.sec
Date.Offset = DS.Offset
end
return Date
end
-- Standard date fallback
local MatchTime = '(%d%d%d%d)(%d%d)(%d%d)T(%d%d)(%d%d)(%d%d)'
local MatchDate = '(%d%d%d%d)(%d%d)(%d%d)'
if s:match(MatchTime) then
Date.year, Date.month, Date.day, Date.hour, Date.min, Date.sec = s:match(MatchTime)
Date.Offset = DS.Offset
else
Date.year, Date.month, Date.day = s:match(MatchDate)
end
return Date
end
},
RememberTheMilk = {
MatchLink = '<link.-rel=.-alternate.-href=["\'](.-)["\']',
MatchItem = '<entry.-</entry>',
MatchItemID = '<id.->(.-)</id>',
MatchItemLink = '<link.-href=["\'](.-)["\']',
MatchItemDesc = '<summary.->(.-)</summary>',
MatchItemDate = '<span class=["\']rtm_due_value["\']>(.-)</span>',
MergeItems = false,
ParseDate = function(s)
local Date = {}
local MatchTime = '%a%a%a (%d+) (%a%a%a) (%d+) at (%d+)%:(%d+)(%a%a)' -- e.g. 'Wed 7 Nov 12 at 3:17PM'
local MatchDate = '%a%a%a (%d+) (%a%a%a) (%d+)' -- e.g. 'Tue 25 Dec 12'
if s:match(MatchTime) then
Date.day, Date.month, Date.year, Date.hour, Date.min, Date.Meridiem = s:match(MatchTime)
elseif s:match(MatchDate) then
Date.day, Date.month, Date.year = s:match(MatchDate)
end
return Date
end
}
}
end
-------------------------
function IdentifyType(s)
-- COLLAPSE CONTAINER TAGS
for _, v in ipairs{ 'item', 'entry' } do
s = s:gsub('<'..v..'.->.+</'..v..'>', '<'..v..'></'..v..'>') -- e.g. '<entry.->.+</entry>' --> '<entry></entry>'
end
--DEFINE RSS MARKER TESTS
--Each of these test functions will be run in turn, until one of them gets a solid match on the format type.
local TestRSS = {
function(a)
-- If the feed contains these tags outside of <item> or <entry>, RSS is confirmed.
for _, v in ipairs{ '<rss', '<channel', '<lastBuildDate', '<pubDate', '<ttl', '<description' } do
if a:match(v) then
return 'RSS'
end
end
return false
end,
function(a)
-- Alternatively, if the feed contains these tags outside of <item> or <entry>, Atom is confirmed.
for _, v in ipairs{ '<feed', '<subTitle' } do
if a:match(v) then
return 'Atom'
end
end
return false
end,
function(a)
-- Alternatively, if the feed contains these tags ICAL is confirmed.
for _, v in ipairs{ 'DTSTART', 'DTEND' } do
if a:match(v) then
return 'Ical'
end
end
return false
end,
function(a)
-- If no markers are present, we search for <item> or <entry> tags to confirm the type.
local HaveItems = a:match('<item')
local HaveEntries = a:match('<entry')
local HaveIcal = a:match('DTSTART')
if HaveItems and not HaveEntries then
return 'RSS'
elseif HaveEntries and not HaveItems then
return 'Atom'
elseif HaveIcal then
return 'Ical'
else
-- If both kinds of tags are present, and no markers are given, then I give up
-- because your feed is ridiculous. And if neither tag is present, then no type
-- can be confirmed (and there would be no usable data anyway).
return false
end
end
}
-- RUN RSS MARKER TESTS
local Class = false
for _, v in ipairs(TestRSS) do
Class = v(s)
if Class then break end
end
-- DETECT SUBTYPE AND RETURN
if Class == 'RSS' then
return 'RSS'
elseif Class == 'Atom' then
if s:match('xmlns:gCal') then
return 'iCalendar'
elseif s:match('<subTitle>rememberthemilk.com</subTitle>') then
return 'RememberTheMilk'
else
return 'Atom'
end
elseif Class == 'Ical' then
return 'iCalendar'
else
return false
end
end
-------------------------
function IdentifyDate(s, t)
local Date = nil
Date = s and Types[t].ParseDate(s) or {}
Date.year = tonumber(Date.year) or nil
Date.month = tonumber(Date.month) or MonthAcronyms[Date.month] or nil
Date.day = tonumber(Date.day) or nil
Date.hour = tonumber(Date.hour) or nil
Date.min = tonumber(Date.min) or nil
Date.sec = tonumber(Date.sec) or 0
-- FIND ENOUGH ELEMENTS, OR DEFAULT TO RETRIEVAL DATE
local RealDate, AllDay
if (Date.year and Date.month and Date.day) then
RealDate = 1
-- DETECT ALL-DAY EVENT
if (Date.hour and Date.min) then
AllDay = 0
else
AllDay = 1
Date.hour = 0
Date.min = 0
end
-- GET CURRENT LOCAL TIME, UTC OFFSET
-- These values are referenced in several procedures below.
local UTC = os.date('*t')
local LocalTime = os.date('*t')
local DaylightSavings = LocalTime.isdst and 3600 or 0
local LocalOffset = os.time(LocalTime) - os.time(UTC) + DaylightSavings
-- CHANGE 12-HOUR to 24-HOUR
if Date.Meridiem then
if (Date.Meridiem == 'AM') and (Date.hour == 12) then
Date.hour = 0
elseif (Date.Meridiem == 'PM') and (Date.hour < 12) then
Date.hour = Date.hour + 12
end
end
-- FIND CLOSEST MATCH FOR TWO-DIGIT YEAR
if Date.year < 100 then
local CurrentYear = LocalTime.year
local CurrentCentury = math.floor(CurrentYear / 100) * 100
local IfThisCentury = CurrentCentury + Date.year
local IfNextCentury = CurrentCentury + Date.year + 100
if math.abs(CurrentYear - IfThisCentury) < math.abs(CurrentYear - IfNextCentury) then
Date.year = IfThisCentury
else
Date.year = IfNextCentury
end
end
-- GET INPUT OFFSET FROM UTC (OR DEFAULT TO LOCAL)
if (Date.Offset) and (Date.Offset ~= '') then
if Date.Offset:match('%a') then
Date.Offset = TimeZones[Date.Offset] and (TimeZones[Date.Offset] * 3600) or 0
elseif Date.Offset:match('%d') then
local Direction, Hours, Minutes = Date.Offset:match('^([^%d]-)(%d+)[^%d]-(%d%d)')
Direction = Direction:match('%-') and -1 or 1
Hours = tonumber(Hours) * 3600
Minutes = tonumber(Minutes) and (tonumber(Minutes) * 60) or 0
Date.Offset = (Hours + Minutes) * Direction
end
else
Date.Offset = LocalOffset
end
-- RETURN CONVERTED DATE
Date = os.time(Date) + LocalOffset - Date.Offset
else
-- NO USABLE DATE FOUND; USE RETRIEVAL DATE INSTEAD
RealDate = 0
AllDay = 0
Date = os.time()
end
return Date, AllDay, RealDate
end
-----------------------------------------------------------------------
-- EVENT FILE MODULE
function EventFile_Initialize()
local EventFiles = {}
local AllEventFiles = SELF:GetOption('EventFile', '')
for EventFile in AllEventFiles:gmatch('[^%|]+') do
table.insert(EventFiles, EventFile)
end
for i, v in ipairs(Feeds) do
local EventFile = EventFiles[i] or SELF:GetName()..'_Feed'..i..'Events.xml'
Feeds[i].EventFile = SKIN:MakePathAbsolute(EventFile)
end
end
function EventFile_Update(a)
local f = a or f
local WriteEvents = SELF:GetNumberOption('WriteEvents', 0)
if (WriteEvents == 1) and (Feeds[f].Type == 'iCalendar') then
-- CREATE XML TABLE
local WriteLines = {}
table.insert(WriteLines, '<EventFile Title="'..Feeds[f].Title..'">')
for i, v in ipairs(Feeds[f]) do
local ItemDate = os.date('*t', v.Date)
table.insert(WriteLines, '<Event Month="'..ItemDate['month']..'" Day="'..ItemDate['day']..'" Desc="'..v.Title..'"/>')
end
table.insert(WriteLines, '</EventFile>')
-- WRITE FILE
local WriteFile = io.output(Feeds[f].EventFile, 'w')
if WriteFile then
local WriteContent = table.concat(WriteLines, '\r\n')
WriteFile:write(WriteContent)
WriteFile:close()
else
SKIN:Bang('!Log', SELF:GetName()..': cannot open file: '..Feeds[f].EventFile)
end
end
end
-----------------------------------------------------------------------
-- HISTORY FILE MODULE
function HistoryFile_Initialize()
-- DETERMINE FILEPATH
HistoryFile = SELF:GetOption('HistoryFile', SELF:GetName()..'History.xml')
HistoryFile = SKIN:MakePathAbsolute(HistoryFile)
-- CREATE HISTORY DATABASE
History = {}
-- CHECK IF FILE EXISTS
local ReadFile = io.open(HistoryFile)
if ReadFile then
local ReadContent = ReadFile:read('*all')
ReadFile:close()
-- PARSE HISTORY FROM LAST SESSION
for ReadFeedURL, ReadFeed in ReadContent:gmatch('<feed URL=(%b"")>(.-)</feed>') do
local ReadFeedURL = ReadFeedURL:match('^"(.-)"$')
History[ReadFeedURL] = {}
for ReadItem in ReadFeed:gmatch('<item>(.-)</item>') do
local Item = {}
for Key, Value in ReadItem:gmatch('<(.-)>(.-)</.->') do
Value = Value:gsub('<', '<')
Value = Value:gsub('>', '>')
Item[Key] = Value
end
Item.Date = tonumber(Item.Date) or Item.Date
Item.Unread = tonumber(Item.Unread)
table.insert(History[ReadFeedURL], Item)
end
end
end
-- ADD HISTORY TO MAIN DATABASE
-- For each feed, if URLs match, add all contents from History[h] to Feeds[f].
for f, Feed in ipairs(Feeds) do
local h = Feed.Measure:GetOption('URL')
Feeds[f].URL = h
if History[h] then
for _, Item in ipairs(History[h]) do
table.insert(Feeds[f], Item)
end
end
end
end
function HistoryFile_Update(a)
local f = a or f
-- CLEAR AND REBUILD HISTORY
local h = Feeds[f].URL
History[h] = {}
for i, Item in ipairs(Feeds[f]) do
table.insert(History[h], Item)
end
-- WRITE HISTORY IF REQUESTED
WriteHistory()
end
function WriteHistory()
local WriteHistory = SELF:GetNumberOption('WriteHistory', 0)
if WriteHistory == 1 then
-- GENERATE XML TABLE
local WriteLines = {}
for WriteURL, WriteFeed in pairs(History) do
table.insert(WriteLines, string.format( '<feed URL=%q>', WriteURL))
for _, WriteItem in ipairs(WriteFeed) do
table.insert(WriteLines, '\t<item>')
for Key, Value in pairs(WriteItem) do
Value = string.gsub(Value, '<', '<')
Value = string.gsub(Value, '>', '>')
table.insert(WriteLines, string.format( '\t\t<%s>%s</%s>', Key, Value, Key))
end
table.insert(WriteLines, '\t</item>')
end
table.insert(WriteLines, '</feed>')
end
-- WRITE XML TO FILE
local WriteFile = io.open(HistoryFile, 'w')
if WriteFile then
local WriteContent = table.concat(WriteLines, '\n')
WriteFile:write(WriteContent)
WriteFile:close()
else
SKIN:Bang('!Log', SELF:GetName()..': cannot open file: '..HistoryFile)
end
end
end
function ClearHistory()
local DeleteFile = io.open(HistoryFile)
if DeleteFile then
DeleteFile:close()
os.remove(HistoryFile)
SKIN:Bang('!Log', SELF:GetName()..': deleted history cache at '..HistoryFile)
end
SKIN:Bang('!Refresh')
end
-----------------------------------------------------------------------
-- CONSTANTS
TimeZones = {
IDLW = -12, -- International Date Line West
NT = -11, -- Nome
CAT = -10, -- Central Alaska
HST = -10, -- Hawaii Standard
HDT = -9, -- Hawaii Daylight
YST = -9, -- Yukon Standard
YDT = -8, -- Yukon Daylight
PST = -8, -- Pacific Standard
PDT = -7, -- Pacific Daylight
MST = -7, -- Mountain Standard
MDT = -6, -- Mountain Daylight
CST = -6, -- Central Standard
CDT = -5, -- Central Daylight
EST = -5, -- Eastern Standard
EDT = -4, -- Eastern Daylight
AST = -3, -- Atlantic Standard
ADT = -2, -- Atlantic Daylight
WAT = -1, -- West Africa
GMT = 0, -- Greenwich Mean
UTC = 0, -- Universal (Coordinated)
Z = 0, -- Zulu, alias for UTC
WET = 0, -- Western European
BST = 1, -- British Summer
CET = 1, -- Central European
MET = 1, -- Middle European
MEWT = 1, -- Middle European Winter
MEST = 2, -- Middle European Summer
CEST = 2, -- Central European Summer
MESZ = 2, -- Middle European Summer
FWT = 1, -- French Winter
FST = 2, -- French Summer
EET = 2, -- Eastern Europe, USSR Zone 1
EEST = 3, -- Eastern European Daylight
WAST = 7, -- West Australian Standard
WADT = 8, -- West Australian Daylight
CCT = 8, -- China Coast, USSR Zone 7
JST = 9, -- Japan Standard, USSR Zone 8
EAST = 10, -- Eastern Australian Standard
EADT = 11, -- Eastern Australian Daylight
GST = 10, -- Guam Standard, USSR Zone 9
NZT = 12, -- New Zealand
NZST = 12, -- New Zealand Standard
NZDT = 13, -- New Zealand Daylight
IDLE = 12 -- International Date Line East
}
MonthAcronyms = {
Jan = 1,
Feb = 2,
Mar = 3,
Apr = 4,
May = 5,
Jun = 6,
Jul = 7,
Aug = 8,
Sep = 9,
Oct = 10,
Nov = 11,
Dec = 12
}
how could I send you the whole skin? you will definitely find the error faster when you see the whole thing.eclectic-tech wrote: ↑June 3rd, 2023, 11:35 am Not seeing that with my current Reader.lua script. That seems to be related to the Item.Title, if our scripts are similar...
If you post the Lua script you are currently using, I can compare it to what I have.
Personal Message a link to where I can download it.
I send you a link.eclectic-tech wrote: ↑June 3rd, 2023, 3:39 pm Personal Message a link to where I can download it.
I can look at it, but I can't guarantee the problem will be apparent due to my limited scripting knowledge.
Code: Select all
GoogleCalendar1=https://calendar.google.com/calendar/ical/myID%40gmail.com/private-keynumber/basic.ics
GoogleCalendar2=https://calendar.google.com/calendar/ical/mysubID%40group.calendar.google.com/private-keynumber/basic.ics
GoogleCalendar3=https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-C.ics
Code: Select all
ReaderURL1=#GoogleCalendar1#
ReaderURL2=#GoogleCalendar2#
ReaderURL3=#GoogleCalendar3#?futureevents=true&singleevents=true&orderby=starttime&sortorder=a