It is currently September 29th, 2023, 1:47 pm

Enigma Google Calendar Patch

A package of skins with a "theme" or by a single author
Jerapoc
Posts: 3
Joined: June 7th, 2023, 3:14 pm

Re: Enigma Google Calendar Patch

Post by Jerapoc »

helder065 wrote: June 7th, 2023, 2:42 pm 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


To start, I am super stoked to have found this thread, cause my calendar is throwing the same error. So thanks for keeping this thread alive!

Unfortunately, though, this fix isn't working for me. The invalid feed error goes away, but then it just hangs on "Processing..." and never loads. Any thoughts?
helder065
Posts: 16
Joined: June 7th, 2023, 2:20 pm
Location: France

Re: Enigma Google Calendar Patch

Post by helder065 »

Jerapoc wrote: June 7th, 2023, 3:18 pm To start, I am super stoked to have found this thread, cause my calendar is throwing the same error. So thanks for keeping this thread alive!

Unfortunately, though, this fix isn't working for me. The invalid feed error goes away, but then it just hangs on "Processing..." and never loads. Any thoughts?
the reader.lua script got some trouble with RRULE events, (RepeatRULE)
use a tool to fix repeated events into single events in your google calendar, like GCaltoolkit that can be a solution,
https://www.gcaltoolkit.com/
I'm not affliliated with the software

Cordially
Nicknightfly
Posts: 9
Joined: October 21st, 2022, 9:48 am

Re: Enigma Google Calendar Patch

Post by Nicknightfly »

helder065 wrote: June 7th, 2023, 2:42 pm 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
Hi helder065, I try your workaround with no success, I have 3 tabs, all from google calendar, first my private, second a public calendar and third holidays in my country. Removing the part (?futureevents=true&singleevents=true&orderby=starttime&sortorder=a) from Gcalendar.ini work for the public and the holidays calendars (for holidays I can also leave the part, it's the same) but the personal is stuck in "processing". Any help is appreciate! TIA
helder065
Posts: 16
Joined: June 7th, 2023, 2:20 pm
Location: France

Re: Enigma Google Calendar Patch

Post by helder065 »

Nicknightfly wrote: June 7th, 2023, 3:44 pm Hi helder065, I try your workaround with no success, I have 3 tabs, all from google calendar, first my private, second a public calendar and third holidays in my country. Removing the part (?futureevents=true&singleevents=true&orderby=starttime&sortorder=a) from Gcalendar.ini work for the public and the holidays calendars (for holidays I can also leave the part, it's the same) but the personal is stuck in "processing". Any help is appreciate! TIA
As mentionned above, the reader.lua script got some trouble with RRULE events, (RepeatRULE) and stuck in "processing" -> infinity of dates
or Events that start before the years 2000 ! (like birthday anniversary repeated event ) causing this kind of problem
and some TZID in pattern are also source of this problem


1) Can you check if MeasureFeed1 in rainmeter is correct like the picture (right click rainmeter notification icon -> about -> skins tab)
MeasureFeed should start with BEGIN:VCALENDARPRO....
Image

2) and also check the WebParserSubstitute in GCalendar.ini , the < was missing and there was a " more

Code: Select all

WebParserSubstitute="<![CDATA[":"","]]>":"","/PRE&gt;":"","PRE&gt;":"","/PRE&lt;":"","PRE&lt;":"","&nbsp;":" ","'s Facebook Notifications":"","Top Stories - Google ":"","&quot;":'"',"&amp;":"&","&apos;":"'","&lt;":"<","&gt;":">","&nbsp;":" ","&iexcl;":"¡","&cent;":"¢","&pound;":"£","&curren;":"¤","&yen;":"¥","&brvbar;":"¦","&sect;":"§","&uml;":"¨","&copy;":"©","&ordf;":"ª","&laquo;":"«","&not;":"¬","&shy;":" ","&reg;":"®","&macr;":"¯","&deg;":"°","&plusmn;":"±","&sup2;":"²","&sup3;":"³","&acute;":"´","&micro;":"µ","&para;":"¶","&middot;":"·","&cedil;":"¸","&sup1;":"¹","&ordm;":"º","&raquo;":"»","&frac14;":"¼","&frac12;":"½","&frac34;":"¾","&iquest;":"¿","&Agrave;":"À","&Aacute;":"Á","&Acirc;":"Â","&Atilde;":"Ã","&Auml;":"Ä","&Aring;":"Å","&AElig;":"Æ","&Ccedil;":"Ç","&Egrave;":"È","&Eacute;":"É","&Ecirc;":"Ê","&Euml;":"Ë","&Igrave;":"Ì","&Iacute;":"Í","&Icirc;":"Î","&Iuml;":"Ï","&ETH;":"Ð","&Ntilde;":"Ñ","&Ograve;":"Ò","&Oacute;":"Ó","&Ocirc;":"Ô","&Otilde;":"Õ","&Ouml;":"Ö","&times;":"×","&Oslash;":"Ø","&Ugrave;":"Ù","&Uacute;":"Ú","&Ucirc;":"Û","&Uuml;":"Ü","&Yacute;":"Ý","&THORN;":"Þ","&szlig;":"ß","&agrave;":"à","&aacute;":"á","&acirc;":"â","&atilde;":"ã","&auml;":"ä","&aring;":"å","&aelig;":"æ","&ccedil;":"ç","&egrave;":"è","&eacute;":"é","&ecirc;":"ê","&euml;":"ë","&igrave;":"ì","&iacute;":"í","&icirc;":"î","&iuml;":"ï","&eth;":"ð","&ntilde;":"ñ","&ograve;":"ò","&oacute;":"ó","&ocirc;":"ô","&otilde;":"õ","&ouml;":"ö","&divide;":"÷","&oslash;":"ø","&ugrave;":"ù","&uacute;":"ú","&ucirc;":"û","&uuml;":"ü","&yacute;":"ý","&thorn;":"þ","&yuml;":"ÿ","&OElig;":"Œ","&oelig;":"œ","&Scaron;":"Š","&scaron;":"š","&Yuml;":"Ÿ","&fnof;":"ƒ","&circ;":"ˆ","&tilde;":"˜","&Alpha;":"?","&Beta;":"?","&Gamma;":"G","&Delta;":"?","&Epsilon;":"?","&Zeta;":"?","&Eta;":"?","&Theta;":"T","&Iota;":"?","&Kappa;":"?","&Lambda;":"?","&Mu;":"?","&Nu;":"?","&Xi;":"?","&Omicron;":"?","&Pi;":"?","&Rho;":"?","&Sigma;":"S","&Tau;":"?","&Upsilon;":"?","&Phi;":"F","&Chi;":"?","&Psi;":"?","&Omega;":"O","&alpha;":"a","&beta;":"ß","&gamma;":"?","&delta;":"d","&epsilon;":"e","&zeta;":"?","&eta;":"?","&theta;":"?","&iota;":"?","&kappa;":"?","&lambda;":"?","&mu;":"µ","&nu;":"?","&xi;":"?","&omicron;":"?","&pi;":"p","&rho;":"?","&sigmaf;":"?","&sigma;":"s","&tau;":"t","&upsilon;":"?","&phi;":"f","&chi;":"?","&psi;":"?","&omega;":"?","&thetasym;":"?","&upsih;":"?","&piv;":"?","&ensp;":" ","&emsp;":" ","&thinsp;":" ","&zwnj;":" ","&zwj;":" ","&lrm;":" ","&rlm;":" ","&ndash;":"–","&mdash;":"—","&lsquo;":"‘","&rsquo;":"’","&sbquo;":"‚","&ldquo;":"“","&rdquo;":"”","&bdquo;":"„","&dagger;":"†","&Dagger;":"‡","&bull;":"•","&hellip;":"…","&permil;":"‰","&prime;":"'","&Prime;":"?","&lsaquo;":"‹","&rsaquo;":"›","&oline;":"?","&frasl;":"/","&euro;":"€","&image;":"I","&weierp;":"P","&real;":"R","&trade;":"™","&alefsym;":"?","&larr;":"?","&uarr;":"?","&rarr;":"?","&darr;":"?","&harr;":"?","&crarr;":"?","&lArr;":"?","&uArr;":"?","&rArr;":"?","&dArr;":"?","&hArr;":"?","&forall;":"?","&part;":"?","&exist;":"?","&empty;":"Ø","&nabla;":"?","&isin;":"?","&notin;":"?","&ni;":"?","&prod;":"?","&sum;":"?","&minus;":"-","&lowast;":"*","&radic;":"v","&prop;":"?","&infin;":"8","&ang;":"?","&and;":"?","&or;":"?","&cap;":"n","&cup;":"?","&int;":"?","&there4;":"?","&sim;":"~","&cong;":"?","&asymp;":"˜","&ne;":"?","&equiv;":"=","&le;":"=","&ge;":"=","&sub;":"?","&sup;":"?","&nsub;":"?","&sube;":"?","&supe;":"?","&oplus;":"?","&otimes;":"?","&perp;":"?","&sdot;":"·","&vellip;":"?","&lceil;":"?","&rceil;":"?","&lfloor;":"?","&rfloor;":"?","&lang;":"<","&rang;":">","&loz;":"?","&spades;":"?","&clubs;":"?","&hearts;":"?","&diams;":"?"

3) One thing to check if GoogleCalendarWriteEvents=1 in GCalendar.ini
the path for storing calendar doesn't exist
ReaderEventFile=#@#User\Calendars\GoogleCalendar1.xml|#@#User\Calendars\GoogleCalendar2.xml|#@#User\Calendars\GoogleCalendar3.xml
create the subpath to store locally events
C:\Users\YOURNAME\Documents\Rainmeter\Skins\GoogleCalendar\@Resources\User\Calendars
for storing GoogleCalendar1.xml, GoogleCalendar2.xml and GoogleCalendar3.xml
Image

4) The last option is to change the pattern for Monthly and Yearly repeated events in reader.lua ,

for monthly events go to line 411
replace s:match('DTSTART;VALUE=DATE:%d%d%d%d%d%d(%d%d)') with s:match('DTSTART;[^:]+:%d%d%d%d%d%d(%d%d)')

for Yearly events go to line 427
replace s:match('DTSTART;VALUE=DATE:(%d%d%d%d)(%d%d)(%d%d)') with s:match('DTSTART;[^:]+:(%d%d%d%d)(%d%d)(%d%d)')

Code: Select all

	-- Monthly recurring events
	elseif s:match('FREQ=MONTHLY') then
		Date.year = os.date('%Y')
		Date.month = os.date('%m')
		Date.day = s:match('DTSTART;[^:]+:%d%d%d%d%d%d(%d%d)')
		--Date.day = s:match('DTSTART;VALUE=DATE:%d%d%d%d%d%d(%d%d)')   <-OLD MONTH PATTERN AS COMMENT
		-- 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;[^:]+:(%d%d%d%d)(%d%d)(%d%d)')
		--Date.year, Date.month, Date.day = s:match('DTSTART;VALUE=DATE:(%d%d%d%d)(%d%d)(%d%d)') <-OLD YEAR PATTERN AS COMMENT
		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

Save the reader.lua file and refresh your skin

the time is still stuck "00:00" for allday event and for repeated yearly and monthly events
Image



Cdt,
helder065
Posts: 16
Joined: June 7th, 2023, 2:20 pm
Location: France

Re: Enigma Google Calendar Patch

Post by helder065 »

Hello

i have made some adjusment for the reader.lua file who was stuck in processing somes rules.

here is a new code

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)
	---------SET YOUR LOCAL TIMESTAMP--------------
	--local Timestamp = SELF:GetOption('Timestamp', '%I:%M %p %A %b %d')       --08:OO PM Monday June 05
	--local Timestamp = SELF:GetOption('Timestamp', '%H:%M %a %d %b')        --20:00 Mon 05 Jun
	--local Timestamp = SELF:GetOption('Timestamp', '%Y %H:%M %a %d %b')     --2023 20:00 Mon 05 Jun 
	local Timestamp = SELF:GetOption('Timestamp', '%H:%M %a %d %b')        --20:00 Mon 05 Jun
	--print(Timestamp , "timestamp 238")

	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 , "item title 272)
		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
					
				------------ADDED DAILY SCRIPT-------
				-- Daily recurring events
				elseif s:match('FREQ=DAILY') then
					--print("Debug Daily true 414")
					Date.year = os.date('%Y')
					Date.month = os.date('%m')
					Date.day = os.date('%d')
					Date.hour = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT(%d%d)%d%d')
					--print(Date.hour," Debug Hour daily true 419")
					Date.min = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT%d%d(%d%d)')
					--print(Date.min," Debug min daily true 421")
					-------------------------------------
					-- If the date is in the past, then add a day
					if os.time(Date) < os.time() then
					  local NbrJourMois = os.date("%d", os.time({year = Date.year, month = Date.month + 1, day = 0}))
					  if tonumber(Date.day) < tonumber(NbrJourMois) then
						Date.day = Date.day + 1
					  else
						Date.day = 1
						Date.month = Date.month + 1
						if Date.month > 12 then
						  Date.month = 1
						  Date.year = Date.year + 1
						end
					  end
					end

					return Date
				-------------------------------------------------------
				
				-- Monthly recurring events
				elseif s:match('FREQ=MONTHLY') then
					--print("Debug Monthly true 413")
					Date.year = os.date('%Y')
					Date.month = os.date('%m')
					Date.day = s:match('DTSTART;[^:]+:%d%d%d%d%d%d(%d%d)')
					-----------ADDED MONTHLY SCRIPT------
					Date.hour = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT(%d%d)%d%d')
					--print(Date.hour," Debug Hour Monthly true 449")
					Date.min = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT%d%d(%d%d)')
					--print(Date.min," Debug min Monthly true 451")
					-------------------------------------
					-- 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;[^:]+:(%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
					------------ADDED YEARLY SCRIPT---------------
					--print("Debug YEARLY true 474")
					Date.hour = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT(%d%d)%d%d')
					--print(Date.hour," Debug Hour Monthly true 476")
					Date.min = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT%d%d(%d%d)')
					--print(Date.min," Debug min Monthly true 478")
					----------------------------------------------
					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)
	--print(s, " s656")
	local Date = nil
	
	Date = s and Types[t].ParseDate(s) or {}
	--print(Date, " date660")
	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
	--print("DEBUG ",Date.year," ",Date.month," ",Date.day," ",Date.hour," ",Date.min," ",Date.sec," print667")
	-- 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
			--print("DEBUG : all day true 681")
		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)
		--print(EventFile) -> C:\Users\MyName\Documents\Rainmeter\Skins\GoogleCalendar\@Resources\User\Calendars\GoogleCalendar1.xml also path for ...2.xml  and ...3.xml
	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
	}

Select All and copy past to your @Resources\Measures\Reader.lua file

Here the result
Image

If you have an "Invalid feed data" goto a previous post https://forum.rainmeter.net/viewtopic.php?t=27623&start=70#p215583

Maybe for a future update of the Reader.lua specfic RRULES every two days, three days...

Enjoy.
Jerapoc
Posts: 3
Joined: June 7th, 2023, 3:14 pm

Re: Enigma Google Calendar Patch

Post by Jerapoc »

helder065 wrote: June 7th, 2023, 3:36 pm the reader.lua script got some trouble with RRULE events, (RepeatRULE)
use a tool to fix repeated events into single events in your google calendar, like GCaltoolkit that can be a solution,
https://www.gcaltoolkit.com/
I'm not affliliated with the software

Cordially
Thanks for all your help, i really appreciate it! I am, unfortunately, having a hard time figuring out gcaltoolkit. Do you know how i would go about converting all my events into singles? I've got my calendar loaded and whatnot, it just doesnt seem to save the events as single. Thanks!

EDIT: I wasn't able to figure it out with gcaltoolkit, but I was able to accomplish it with the instructions from this site: https://www.iorad.com/player/2079189/How-to-convert-recurring-events-to-individual-events-in-Google-Calendar#trysteps-12

And a mix of excel/google sheets.

So, long story short, now that all my events are no longer recurring but standalone, everything now works! Thank you so much for all the help!
helder065
Posts: 16
Joined: June 7th, 2023, 2:20 pm
Location: France

Re: Enigma Google Calendar Patch

Post by helder065 »

Jerapoc wrote: June 8th, 2023, 5:36 pm Thanks for all your help, i really appreciate it! I am, unfortunately, having a hard time figuring out gcaltoolkit. Do you know how i would go about converting all my events into singles? I've got my calendar loaded and whatnot, it just doesnt seem to save the events as single. Thanks!
Hi Jerapoc,
before using Gcaltoolkit, did you use the Reader.lua a bit upgraded from original in previous post

https://forum.rainmeter.net/viewtopic.php?t=27623&start=80#p215598

let me know if it sounds good.

Gonna addin some extrarules.

Cdt
Jerapoc
Posts: 3
Joined: June 7th, 2023, 3:14 pm

Re: Enigma Google Calendar Patch

Post by Jerapoc »

helder065 wrote: June 8th, 2023, 6:54 pm Hi Jerapoc,
before using Gcaltoolkit, did you use the Reader.lua a bit upgraded from original in previous post

https://forum.rainmeter.net/viewtopic.php?t=27623&start=80#p215598

let me know if it sounds good.

Gonna addin some extrarules.

Cdt
I did update my reader.lua first, but it didn't fix my problem. After i got everything working again, you're reader.lua code worked, though!
helder065
Posts: 16
Joined: June 7th, 2023, 2:20 pm
Location: France

Re: Enigma Google Calendar Patch

Post by helder065 »

helder065 wrote: June 8th, 2023, 2:12 pm Hello

i have made some adjusment for the reader.lua file who was stuck in processing somes rules.

here is a new code

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)
	---------SET YOUR LOCAL TIMESTAMP--------------
	--local Timestamp = SELF:GetOption('Timestamp', '%I:%M %p %A %b %d')       --08:OO PM Monday June 05
	--local Timestamp = SELF:GetOption('Timestamp', '%H:%M %a %d %b')        --20:00 Mon 05 Jun
	--local Timestamp = SELF:GetOption('Timestamp', '%Y %H:%M %a %d %b')     --2023 20:00 Mon 05 Jun 
	local Timestamp = SELF:GetOption('Timestamp', '%H:%M %a %d %b')        --20:00 Mon 05 Jun
	--print(Timestamp , "timestamp 238")

	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 , "item title 272)
		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
					
				------------ADDED DAILY SCRIPT-------
				-- Daily recurring events
				elseif s:match('FREQ=DAILY') then
					--print("Debug Daily true 414")
					Date.year = os.date('%Y')
					Date.month = os.date('%m')
					Date.day = os.date('%d')
					Date.hour = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT(%d%d)%d%d')
					--print(Date.hour," Debug Hour daily true 419")
					Date.min = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT%d%d(%d%d)')
					--print(Date.min," Debug min daily true 421")
					-------------------------------------
					-- If the date is in the past, then add a day
					if os.time(Date) < os.time() then
					  local NbrJourMois = os.date("%d", os.time({year = Date.year, month = Date.month + 1, day = 0}))
					  if tonumber(Date.day) < tonumber(NbrJourMois) then
						Date.day = Date.day + 1
					  else
						Date.day = 1
						Date.month = Date.month + 1
						if Date.month > 12 then
						  Date.month = 1
						  Date.year = Date.year + 1
						end
					  end
					end

					return Date
				-------------------------------------------------------
				
				-- Monthly recurring events
				elseif s:match('FREQ=MONTHLY') then
					--print("Debug Monthly true 413")
					Date.year = os.date('%Y')
					Date.month = os.date('%m')
					Date.day = s:match('DTSTART;[^:]+:%d%d%d%d%d%d(%d%d)')
					-----------ADDED MONTHLY SCRIPT------
					Date.hour = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT(%d%d)%d%d')
					--print(Date.hour," Debug Hour Monthly true 449")
					Date.min = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT%d%d(%d%d)')
					--print(Date.min," Debug min Monthly true 451")
					-------------------------------------
					-- 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;[^:]+:(%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
					------------ADDED YEARLY SCRIPT---------------
					--print("Debug YEARLY true 474")
					Date.hour = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT(%d%d)%d%d')
					--print(Date.hour," Debug Hour Monthly true 476")
					Date.min = s:match('DTSTART;[^:]+:%d%d%d%d%d%d%d%dT%d%d(%d%d)')
					--print(Date.min," Debug min Monthly true 478")
					----------------------------------------------
					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)
	--print(s, " s656")
	local Date = nil
	
	Date = s and Types[t].ParseDate(s) or {}
	--print(Date, " date660")
	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
	--print("DEBUG ",Date.year," ",Date.month," ",Date.day," ",Date.hour," ",Date.min," ",Date.sec," print667")
	-- 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
			--print("DEBUG : all day true 681")
		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)
		--print(EventFile) -> C:\Users\MyName\Documents\Rainmeter\Skins\GoogleCalendar\@Resources\User\Calendars\GoogleCalendar1.xml also path for ...2.xml  and ...3.xml
	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
	}

Select All and copy past to your @Resources\Measures\Reader.lua file

Here the result
Image

If you have an "Invalid feed data" goto a previous post https://forum.rainmeter.net/viewtopic.php?t=27623&start=70#p215583

Maybe for a future update of the Reader.lua specfic RRULES every two days, three days...

Enjoy.
shoek
Posts: 32
Joined: March 10th, 2017, 1:47 am

Re: Enigma Google Calendar Patch

Post by shoek »

Thanks to @helder065 and his updated Reader.lua code, I was able to get this working.
However, I had to modify his new code with the following at line 107

Code: Select all

                                if (Item.Title and string.len(Item.Title) == 1) then
                                        Item.Title = RawItem:match('DESCRIPTION:(.-)\n') or nil
                                elseif (Item.Title) then
					Item.Title = Item.Title:gsub('\\', '')
                                end
I also had to remove the query params from ReaderURL1 as noted in a previous post

Finally, not sure if it is necessary, but I update my INI's WebParserSubstitute to what was noted in a previous post

Thank you to everyone helping to keep this skin alive!