It is currently April 20th, 2024, 1:30 am

Enigma Google Calendar Patch

A package of skins with a "theme" or by a single author
Nicknightfly
Posts: 9
Joined: October 21st, 2022, 9:48 am

Re: Enigma Google Calendar Patch

Post by Nicknightfly »

balala wrote: October 24th, 2022, 6:28 pm I'm surprised if you had to add the %y parameter to the TimeStampFormat options, but if you got it working this way, it's good, I believe. Glad if it's working well.
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
User avatar
balala
Rainmeter Sage
Posts: 16147
Joined: October 11th, 2010, 6:27 pm
Location: Gheorgheni, Romania

Re: Enigma Google Calendar Patch

Post by balala »

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!
Glad to help. :thumbup:
Spaldo
Posts: 4
Joined: January 16th, 2023, 11:56 am

Re: Enigma Google Calendar Patch

Post by Spaldo »

I may have missed it, but couldn't see it... What was the best way to remove the 12:00 AM from the all day events and just have the date for those?
tsoares1982
Posts: 1
Joined: March 17th, 2023, 1:05 pm

Re: Enigma Google Calendar Patch

Post by tsoares1982 »

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!)... :)

Hello, i've installed the GoogleCalendar_1.2018.01.09.rmskin and put my secret address in iCal format, but the calendar skin is stucked in "Loading...Processing....".

Is there anything else that i should do??
User avatar
pds
Posts: 79
Joined: April 12th, 2014, 12:52 pm
Location: Slovakia

Re: Enigma Google Calendar Patch

Post by pds »

Hi.

a few days ago I started getting a script error. So far everything worked ok. Don't know what to do with it?
chyba.jpg
You do not have the required permissions to view the files attached to this post.
User avatar
eclectic-tech
Rainmeter Sage
Posts: 5398
Joined: April 12th, 2012, 9:40 pm
Location: Cedar Point, Ohio, USA

Re: Enigma Google Calendar Patch

Post by eclectic-tech »

pds wrote: June 3rd, 2023, 9:32 am Hi.

a few days ago I started getting a script error. So far everything worked ok. Don't know what to do with it?

chyba.jpg
Not seeing that with my current Reader.lua script. That seems to be related to the Item.Title, if our scripts are similar...
Reader.lua

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('&lt;', '<')
					Value = Value:gsub('&gt;', '>')
					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, '<', '&lt;')
					Value = string.gsub(Value, '>', '&gt;')
					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
	}
Compare it to what you have; not counting any special changes you may have made.
User avatar
pds
Posts: 79
Joined: April 12th, 2014, 12:52 pm
Location: Slovakia

Re: Enigma Google Calendar Patch

Post by pds »

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.
how could I send you the whole skin? you will definitely find the error faster when you see the whole thing.
User avatar
eclectic-tech
Rainmeter Sage
Posts: 5398
Joined: April 12th, 2012, 9:40 pm
Location: Cedar Point, Ohio, USA

Re: Enigma Google Calendar Patch

Post by eclectic-tech »

pds wrote: June 3rd, 2023, 11:52 am how could I send you the whole skin? you will definitely find the error faster when you see the whole thing.
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.
User avatar
pds
Posts: 79
Joined: April 12th, 2014, 12:52 pm
Location: Slovakia

Re: Enigma Google Calendar Patch

Post by pds »

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.
I send you a link.
Thank you
helder065
Posts: 18
Joined: June 7th, 2023, 2:20 pm
Location: France

Re: Enigma Google Calendar Patch

Post by helder065 »

Hi,
Since few days, i got "Invalid Feed Format" from only my google calendars (tab1 and 2).
google returning an error 500 html page.

It seems that the readerURL configured in Gcalendar.ini is the cause of the problem
I'm sharing my solution

Mycalendars config

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
I just remove the request part (?futureevents=true&singleevents=true&orderby=starttime&sortorder=a) in GCalendar.ini for the two google calendar
ReaderURL1 and ReaderURL2 like this

Code: Select all

ReaderURL1=#GoogleCalendar1#
ReaderURL2=#GoogleCalendar2#
ReaderURL3=#GoogleCalendar3#?futureevents=true&singleevents=true&orderby=starttime&sortorder=a
Now the 3 tabs works fine.

Cordially