It is currently March 19th, 2024, 9:18 am

FeedReader w/ sorting

RSS, ATOM and other feeds, GMail, Stocks, any information retrieved from the internet
User avatar
wiedzmawiedzma
Posts: 112
Joined: August 18th, 2012, 5:19 pm

Re: FeedReader w/ sorting

Post by wiedzmawiedzma »

Super Skin but the problem is that in Poland rss feeds in 98% look like this:

http://www.tvn24.pl/najnowsze.xml
http://dobreprogramy.pl/rss/rss_progs.xml
http://rss.gazeta.pl/pub/rss/sport.xml

Is it possible to open it with the tip of xml feeds?
User avatar
Mordasius
Posts: 1166
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: FeedReader w/ sorting

Post by Mordasius »

Sorry about that. The reader was primarily intended to sort the BBC and Guardian news feeds and couldn't cope with publication dates in the <pubDate>Tue, 11 Sep 12 07:06:00 +0200</pubDate> format. I've done a gutter-tape and monkey-wrench job on the script and the updated version posted on Deviant Art will handle feeds like those you mentioned.

However, I didn't try to make it work with all time and date formats as Kaleri's latest reader script does a much better job and is more reliable across a wider range of feed types.
User avatar
wiedzmawiedzma
Posts: 112
Joined: August 18th, 2012, 5:19 pm

Re: FeedReader w/ sorting

Post by wiedzmawiedzma »

Once again, a big, very big thanks! This time here on the forum! It works with XML! :bow:
rainusero
Posts: 21
Joined: May 9th, 2012, 10:11 am

Re: FeedReader w/ sorting

Post by rainusero »

What about this RSS:

http://stackoverflow.com/feeds
http://serverfault.com/feeds
http://superuser.com/feeds
http://unix.stackexchange.com/feeds
http://feeds.feedburner.com/niebezpiecznik/
http://freecode.com/?format=atom
http://rss.slashdot.org/Slashdot/slashdotLinux
http://bsdtalk.blogspot.com/feeds/posts/default
http://lcamtuf.blogspot.com/feeds/posts/default?alt=rss

They doesn't work. Is there a possibility to correct this ?

And another problem:
When you switch from feed where there is 10 feeds to another feed where is less than 10 feeds, there is some problem with refresh.
I get e.g. 5 feeds from current set of feeds and another 5 from recently read feeds when i switch to another one. IMHO there should be visible only 5 current feeds instead of 10.
User avatar
Mordasius
Posts: 1166
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: FeedReader w/ sorting

Post by Mordasius »

rainusero wrote:They doesn't work. Is there a possibility to correct this ?
The easiest way to correct this will be to use Kaleri's latest reader script but I haven't got around to adapting it to show the bulleting of recent feeds yet. It shouldn't be that difficult as Kaleri's script already handles various timestamp formats and puts the publication dates into tables where they can be easily sorted.
rainusero
Posts: 21
Joined: May 9th, 2012, 10:11 am

Re: FeedReader w/ sorting

Post by rainusero »

Dear Mordasius,

1. Will you be able to fully (or almost fully) adapt Kaleri's script to your skin to handle various ATOM/RSS formats ?

2. Another sample RSS that doesn't work (shows Empty. on RSS list): http://dozzie.jarowit.net/usenix-login.xml. Could you check in ?

3. Refresh problem
When you switch from feed where there is 10 feeds to another feed where is less than 10 feeds, there is some problem with refresh.
I get e.g. 5 feeds from current set of feeds and another 5 from recently read feeds when I switch to another one. IMHO there should be visible only 5 current feeds instead of 10.

4. Last but not least. If there is an issue when some RSS/ATOM doesn't work (like this: http://storagedevicessafety.info/feed/) a message is shown: "Matching Error. No valid feed found". That's okay and quite normal behavior. But there is one case (I found only one case - perhaps there is more than one) where above rule doesn't apply.

Here it is: http://www.linux.com/rss/feeds.php. There is something wrong with this feed. If you type it in your browser address bar you'll get redirection to another address: http://archive09.linux.com/rss/feeds.php. Something doesn't work. It's obvious but this is not our fault and it's not so important :)

Significant is that instead of message, I mentioned above (Matching error, etc...), entire rainmeter is hanging up. I must open process manager, kill rainmeter.exe process, then open my feed list, delete "invalid" entry (http://www.linux.com/rss/feeds.php in this case), restart application and after that everything works fine. IMHO this is not usual and proper behavior and should be immediately corrected. Displaying a message "Matching Error. No valid feed found" in every case when there's something wrong with RSS should be good enough. Without hanging up entire application.

It is very inconvenient and annoying to watch all the time if some RSS/ATOM is broken in this way, isn't it ? Please check your lua script for that "contingency". This time it was only one feed but in near future... who knows.

I wonder, I'm just only one person with this issue...
User avatar
Mordasius
Posts: 1166
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: FeedReader w/ sorting

Post by Mordasius »

Thanks for reporting the bugs. All of the problems you mention above were caused by the way the Lua script was handling different date formats. I'm sure there must have been other people with the same problems but they just haven't reported them.

I've now incorporated Kaleri's latest Reader script which seems to fix most, but not all, of the errors you mentioned. I just made a few changes to sort the feeds by date/time and 'bullet' those which are less than 30 minutes old (you can change the value). I've also trimmed the title of the Rainmeter Forums threads as in the original version of the FeedReader.

Download a new copy from Deviant Art and let me know if you find any more errors.
rainusero
Posts: 21
Joined: May 9th, 2012, 10:11 am

Re: FeedReader w/ sorting

Post by rainusero »

Great work.

But where is possibility to increase number of feed channels to more than 5 and increase number of visible feeds to more than 9.

In your recent version there was a variable called NumberOfFeeds. Where is it now ?
Could you implement it again ?

Could you explain what does this part do ?
WebParserSubstitute="<![CDATA[":"","]]>":"","/PRE>":"","PRE>":"","&nbsp;":" ","'s Facebook Notifications":"","Top Stories - Google ":""
Last edited by rainusero on October 2nd, 2012, 2:32 pm, edited 1 time in total.
User avatar
Kaelri
Developer
Posts: 1721
Joined: July 25th, 2009, 4:47 am
Contact:

Re: FeedReader w/ sorting

Post by Kaelri »

@Mordasius: Let me give you my most recent working copy of the Reader script. It's a little messy, but I tested it on each of the feeds above, and it seems to handle all the dates.

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 Input(a)
	local f = a or f

	local Raw = Feeds[f].Measure:GetStringValue()

	if Raw == '' then
		Feeds[f].Error = {
			Description = 'Waiting for data from WebParser.',
			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
		Feeds[f].Title = Raw:match('<title.->(.-)</title>') or 'Untitled'
		Feeds[f].Link  = Raw:match(Type.MatchLink)          or nil

		local Items = {}
		for RawItem in Raw:gmatch(Type.MatchItem) do
			local Item  = {}

			-- MATCH RAW DATA
			Item.Unread = 1
			Item.Title  = RawItem:match('<title.->(.-)</title>') or nil
			Item.Link   = RawItem:match(Type.MatchItemLink)      or nil
			Item.Desc   = RawItem:match(Type.MatchItemDesc)      or nil
			Item.Date   = RawItem:match(Type.MatchItemDate)      or RawItem:match(Type.MatchItemDate2) 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)

			table.insert(Items, Item)
		end
		
		-- 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
				table.insert(Feeds[f], 1, Items[i])
			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 on %d %B %Y')

	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['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..'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 ''
		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>',
			MatchItemDate2 = '<dc:date>(.-)</dc:date>',
			MergeItems     = true,
			ParseDate      = function(s)
				print(s)
				local Date = {}
				local MatchTime = '%a+, (%d+) (%a+) (%d+) (%d+)%:(%d+)%:(%d+) (.-)$'
				local MatchDate = '%a+, (%d+) (%a+) (%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 = '<updated.->(.-)</updated>',
			MergeItems    = true,
			ParseDate     = function(s)
				local Date = {}
				local MatchTime = '(%d+)%-(%d+)%-(%d+)T(%d+)%:(%d+)%:(%d+)(.-)$'
				local MatchDate = '(%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
			},
		GoogleCalendar = {
			MatchLink     = '<link.-rel=.-alternate.-href=["\'](.-)["\']',
			MatchItem     = '<entry.-</entry>',
			MatchItemID   = '<id.->(.-)</id>',
			MatchItemLink = '<link.-href=["\'](.-)["\']',
			MatchItemDesc = '<summary.->(.-)</summary>',
			MatchItemDate = 'startTime=["\'](.-)["\']',
			MergeItems    = false,
			ParseDate     = function(s)
				local Date = {}
				local MatchTime = '(%d+)%-(%d+)%-(%d+)T(%d+)%:(%d+)%:(%d+)%.%d+(.-)$'
				local MatchDate = '(%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
			},
		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+ (%d+) (%a+) (%d+) at (%d+)%:(%d+)(%a+)' -- e.g. 'Wed 7 Nov 12 at 3:17PM'
				local MatchDate = '%a+ (%d+) (%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)
			-- 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')

			if HaveItems and not HaveEntries then
				return 'RSS'
			elseif HaveEntries and not HaveItems then
				return 'Atom'
			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 'GoogleCalendar'
		elseif s:match('<subtitle>rememberthemilk.com</subtitle>') then
			return 'RememberTheMilk'
		else
			return 'Atom'
		end
	else
		return false
	end
end

-------------------------

function IdentifyDate(s, t)
	-- PARSE STRING BY TYPE
	local Date
	if s then
		Date = Types[t].ParseDate(s) or Types.RSS.ParseDate(s) or Types.Atom.ParseDate(s) or {}
	else
		Date = {}
	end

	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 == 'GoogleCalendar') then
		-- CREATE XML TABLE
		local WriteLines = {}
		table.insert(WriteLines, '<EventFile Title="'..Feeds[f].Title..'">')
		for i, v in ipairs(Feeds[f]) do
			local ItemDate = os.date('*t', v.Date)
			table.insert(WriteLines, '<Event Month="'..ItemDate['month']..'" Day="'..ItemDate['day']..'" Desc="'..v.Title..'"/>')
		end
		table.insert(WriteLines, '</EventFile>')
		
		-- WRITE FILE
		local WriteFile = io.output(Feeds[f].EventFile, 'w')
		if WriteFile then
			local WriteContent = table.concat(WriteLines, '\r\n')
			WriteFile:write(WriteContent)
			WriteFile:close()
		else
			SKIN:Bang('!Log', SELF:GetName()..': cannot open file: '..Feeds[f].EventFile)
		end
	end
end

-----------------------------------------------------------------------
-- HISTORY FILE MODULE

function HistoryFile_Initialize()
	-- DETERMINE FILEPATH
	HistoryFile = SELF:GetOption('HistoryFile', SELF:GetName()..'History.xml')
	HistoryFile = SKIN:MakePathAbsolute(HistoryFile)

	-- CREATE HISTORY DATABASE
	History = {}

	-- CHECK IF FILE EXISTS
	local ReadFile = io.open(HistoryFile)
	if ReadFile then
		local ReadContent = ReadFile:read('*all')
		ReadFile:close()

		-- PARSE HISTORY FROM LAST SESSION
		for ReadFeedURL, ReadFeed in ReadContent:gmatch('<feed URL=(%b"")>(.-)</feed>') do
			local ReadFeedURL = ReadFeedURL:match('^"(.-)"$')
			History[ReadFeedURL] = {}
			for ReadItem in ReadFeed:gmatch('<item>(.-)</item>') do
				local Item = {}
				for Key, Value in ReadItem:gmatch('<(.-)>(.-)</.->') do
					Value = Value:gsub('<', '<')
					Value = Value:gsub('>', '>')
					Item[Key] = Value
				end
				Item.Date = tonumber(Item.Date) or Item.Date
				Item.Unread = tonumber(Item.Unread)
				table.insert(History[ReadFeedURL], Item)
			end
		end
	end

	-- ADD HISTORY TO MAIN DATABASE
	-- For each feed, if URLs match, add all contents from History[h] to Feeds[f].
	for f, Feed in ipairs(Feeds) do
		local h = Feed.Measure:GetOption('URL')
		Feeds[f].URL = h
		if History[h] then
			for _, Item in ipairs(History[h]) do
				table.insert(Feeds[f], Item)
			end
		end
	end
end

function HistoryFile_Update(a)
	local f = a or f

	-- CLEAR AND REBUILD HISTORY
	local h = Feeds[f].URL
	History[h] = {}
	for i, Item in ipairs(Feeds[f]) do
		table.insert(History[h], Item)
	end

	-- WRITE HISTORY IF REQUESTED
	WriteHistory()
end

function WriteHistory()
	local WriteHistory = SELF:GetNumberOption('WriteHistory', 0)
	if WriteHistory == 1 then
		-- GENERATE XML TABLE
		local WriteLines = {}
		for WriteURL, WriteFeed in pairs(History) do
			table.insert(WriteLines, string.format(         '<feed URL=%q>', WriteURL))
			for _, WriteItem in ipairs(WriteFeed) do
				table.insert(WriteLines,                    '\t<item>')
				for Key, Value in pairs(WriteItem) do
					Value = string.gsub(Value, '<', '<')
					Value = string.gsub(Value, '>', '>')
					table.insert(WriteLines, string.format( '\t\t<%s>%s</%s>', Key, Value, Key))
				end
				table.insert(WriteLines,                    '\t</item>')
			end
			table.insert(WriteLines,                        '</feed>')
		end

		-- WRITE XML TO FILE
		local WriteFile = io.open(HistoryFile, 'w')
		if WriteFile then
			local WriteContent = table.concat(WriteLines, '\n')
			WriteFile:write(WriteContent)
			WriteFile:close()
		else
			SKIN:Bang('!Log', SELF:GetName()..': cannot open file: '..HistoryFile)
		end
	end
end

function ClearHistory()
	local DeleteFile = io.open(HistoryFile)
	if DeleteFile then
		DeleteFile:close()
		os.remove(HistoryFile)
		SKIN:Bang('!Log', SELF:GetName()..': deleted history cache at '..HistoryFile)
	end
	SKIN:Bang('!Refresh')
end

-----------------------------------------------------------------------
-- CONSTANTS

TimeZones = {
	IDLW = -12, --  International Date Line West 
	NT   = -11, --  Nome 
	CAT  = -10, --  Central Alaska 
	HST  = -10, --  Hawaii Standard 
	HDT  = -9,  --  Hawaii Daylight 
	YST  = -9,  --  Yukon Standard 
	YDT  = -8,  --  Yukon Daylight 
	PST  = -8,  --  Pacific Standard 
	PDT  = -7,  --  Pacific Daylight 
	MST  = -7,  --  Mountain Standard 
	MDT  = -6,  --  Mountain Daylight 
	CST  = -6,  --  Central Standard 
	CDT  = -5,  --  Central Daylight 
	EST  = -5,  --  Eastern Standard 
	EDT  = -4,  --  Eastern Daylight 
	AST  = -3,  --  Atlantic Standard 
	ADT  = -2,  --  Atlantic Daylight 
	WAT  = -1,  --  West Africa 
	GMT  =  0,  --  Greenwich Mean 
	UTC  =  0,  --  Universal (Coordinated) 
	Z    =  0,  --  Zulu, alias for UTC 
	WET  =  0,  --  Western European 
	BST  =  1,  --  British Summer 
	CET  =  1,  --  Central European 
	MET  =  1,  --  Middle European 
	MEWT =  1,  --  Middle European Winter 
	MEST =  2,  --  Middle European Summer 
	CEST =  2,  --  Central European Summer 
	MESZ =  2,  --  Middle European Summer 
	FWT  =  1,  --  French Winter 
	FST  =  2,  --  French Summer 
	EET  =  2,  --  Eastern Europe, USSR Zone 1 
	EEST =  3,  --  Eastern European Daylight 
	WAST =  7,  --  West Australian Standard 
	WADT =  8,  --  West Australian Daylight 
	CCT  =  8,  --  China Coast, USSR Zone 7 
	JST  =  9,  --  Japan Standard, USSR Zone 8 
	EAST = 10,  --  Eastern Australian Standard 
	EADT = 11,  --  Eastern Australian Daylight 
	GST  = 10,  --  Guam Standard, USSR Zone 9 
	NZT  = 12,  --  New Zealand 
	NZST = 12,  --  New Zealand Standard 
	NZDT = 13,  --  New Zealand Daylight 
	IDLE = 12   --  International Date Line East 
	}

MonthAcronyms = {
	Jan = 1,
	Feb = 2,
	Mar = 3,
	Apr = 4,
	May = 5,
	Jun = 6,
	Jul = 7,
	Aug = 8,
	Sep = 9,
	Oct = 10,
	Nov = 11,
	Dec = 12
	}
User avatar
Mordasius
Posts: 1166
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: FeedReader w/ sorting

Post by Mordasius »

Kaelri wrote:I tested it on each of the feeds above, and it seems to handle all the dates.
Fine but it just returns all Rainmeter Forums as a blank.. unless I've messed up somewhere with the paste/merge :???:

( I pasted it over Reader.lua script of EverReader 0.4 to check and same result )
Last edited by Mordasius on October 2nd, 2012, 3:08 pm, edited 1 time in total.
Post Reply