It is currently April 18th, 2024, 3:14 am

Reader

Discuss the use of Lua in Script measures.
User avatar
Mordasius
Posts: 1171
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Universal Feed Reader - adapted for BBC Feeds

Post by Mordasius »

I've been trying to incorporate the sorting of BBC feeds by date and bulleting of recent items in Evereader.

The sorting by date works (and is necessary because BBC feeds are not always sorted chronologicaly) but I'm dashed if I can get the bulleting of recent feeds to work when the skin has more than one feed that can be displayed and flipped through using ArrowNext, Grabber and ArrowPrevious meters.

All I want to do is bullet those items that are new since the last time the [MeasureFeed] was updated according to #UpdateRate# but ignore any changes resulting from the display being updated by using ArrowNext, Grabber or ArrowPrevious.

The bits of code I've added to the Evereader.ini file are limited to:

Code: Select all

[Variables]
LatestPost1=0
LatestPost2=0
LatestPost3=0

URL1=http://feeds.bbci.co.uk/news/rss.xml?edition=uk
URL2=http://feeds.bbci.co.uk/news/science_and_environment/rss.xml
URL3=http://feeds.bbci.co.uk/news/technology/rss.xml

[MeasureLuaScript]
TimedUpdate=1
The stuff I added to the .lua file has been stuffed between
-->> -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
and
-->> END of added stuff ------------------------------------------------<<

Code: Select all

PROPERTIES = {
	FeedMeasureName = '';
	MultipleFeeds = 0;
	VariablePrefix = '';
	MinItems = 0;
	FinishAction = '';
-->>  -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
	TimedUpdate = 0
-->>  END of added stuff ------------------------------------------------<<
}
-- When Rainmeter supports escape characters for bangs, use this function to escape quotes.
function ParseSpecialCharacters(sString)
	sString = string.gsub(sString, '\"', '')
	return sString
end

function Initialize()
	sFeedMeasureName = PROPERTIES.FeedMeasureName
	iMultipleFeeds = tonumber(PROPERTIES.MultipleFeeds)
	sVariablePrefix = PROPERTIES.VariablePrefix
	iMinItems = tonumber(PROPERTIES.MinItems)
	sFinishAction = PROPERTIES.FinishAction
	tFeeds = {}
	tTitles = {}
	tLinks = {}
	tDates = {}
	
-->>  -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
	iTimedUpdate = tonumber(PROPERTIES.TimedUpdate)
	tmText = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
	tmNum = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}
-->>  END of added stuff ------------------------------------------------<<
end

function Update()

	-----------------------------------------------------------------------
	-- INPUT FEED(S)
	
	if iMultipleFeeds == 1 then
		iNumberOfFeeds = tonumber(SKIN:GetVariable(sVariablePrefix..'NumberOfFeeds'))
		for i = 1, iNumberOfFeeds do
			tFeeds[i] = SKIN:GetVariable(sVariablePrefix..'FeedMeasureName'..i)
		end
		iCurrentFeed = tonumber(SKIN:GetVariable(sVariablePrefix..'CurrentFeed'))
		msRaw = SKIN:GetMeasure(tFeeds[iCurrentFeed])
	else
		msRaw = SKIN:GetMeasure(sFeedMeasureName)
	end
	sRaw = msRaw:GetStringValue()
	
	-----------------------------------------------------------------------
	-- DETERMINE FEED FORMAT AND CONTENTS
	
	sPatternFeedTitle = '.-<title.->(.-)</title>'
	sPatternItemTitle = '.-<title.->(.-)</title>'
	if string.match(sRaw, 'xmlns:gCal') then
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
		sPatternFeedLink = '.-<link.-rel=.-alternate.-href=\'(.-)\''
		sPatternItem = '<entry.-</entry>'
		sPatternItemLink = '.-<link.-href=\'(.-)\''
		sPatternItemDate = '.-When: (.-)<'
	elseif string.match(sRaw, '<subtitle>rememberthemilk.com</subtitle>') then
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
		sPatternFeedLink = '.-<link.-rel=.-alternate.-href="(.-)"'
		sPatternItem = '<entry.-</entry>'
		sPatternItemLink = '.-<link.-href="(.-)"'
		sPatternItemDate = '<span class="rtm_due_value">(.-)</span>'
	elseif string.match(sRaw, '<rss.-version=".-".->') then
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<item', "")
		sPatternFeedLink = '.-<link.->(.-)</link>'
		sPatternItem = '<item.-</item>'
		sPatternItemLink = '.-<link.->(.-)</link>'
		sPatternItemDesc = '.-<description.->(.-)</description>'
		sPatternItemDate = '.-<pubDate.->(.-)</pubDate>'
	else
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
		sPatternFeedLink = '.-<link.-href="(.-)"'
		sPatternItem = '<entry.-</entry>'
		sPatternItemLink = '.-<link.-href="(.-)"'
		sPatternItemDesc = '.-<summary.->(.-)</summary>'
		sPatternItemDate = '.-<updated.->(.-)</updated>'
	end
	
	-----------------------------------------------------------------------
	-- ERRORS
	
	sFeedTitle, sFeedLink = string.match(sRaw, sPatternFeedTitle..sPatternFeedLink)
	if not sFeedTitle then
		FeedError('Matching Error', '', 'No valid feed was found.')
		return 'Error: matching feed URL.'
	elseif iNumberOfItems == 0 then
		FeedError(sFeedTitle, '', 'Empty.')
		return 'Error: empty feed.'
	end
	
	-----------------------------------------------------------------------
	-- CREATE DATABASE
	
	sFeedTitle = ParseSpecialCharacters(sFeedTitle)
	sFeedLink = ParseSpecialCharacters(sFeedLink)

	iInit = 0
	for i = 1, iNumberOfItems do
		iItemStart, iItemEnd = string.find(sRaw, sPatternItem, iInit)
		sItem = string.sub(sRaw, iItemStart, iItemEnd)
		tTitles[i] = string.match(sItem, sPatternItemTitle)
		tTitles[i] = ParseSpecialCharacters(tTitles[i])
		tLinks[i] = string.match(sItem, sPatternItemLink)
		tLinks[i] = ParseSpecialCharacters(tLinks[i])
		if string.match(sItem, sPatternItemDate) then
			tDates[i] = string.match(sItem, sPatternItemDate)
			tDates[i] = ParseSpecialCharacters(tDates[i])
		else
			tDates[i] = ''
		end
		iInit = iItemEnd + 1
	end
-->>  -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
--       SET LATEST POST, TIDY UP THE DATE STRING, SORT FEEDS BY DATE  
	sMostRecent = SKIN:GetVariable('LatestPost'..iCurrentFeed) 
	for i = 1, iNumberOfItems do
	    for j = 1, 12 do
			tDates[i]  =  string.gsub(tDates[i], tmText[j] , tmNum[j] )
      	end     
		tDates[i]  =  string.sub(tDates[i],  12, 15)..string.sub(tDates[i],  9, 10)..string.sub(tDates[i],6, 7)..string.sub(tDates[i],17, 18)..string.sub(tDates[i],20,21)..string.sub(tDates[i],23,24)

		if tonumber(tDates[i]) > tonumber(sMostRecent)
			   then tTitles[i] = "• "..tTitles[i] 
			   else tTitles[i] = "  "..tTitles[i] end
	end		
	
	tItems = {}
	    for i = 1, iNumberOfItems do
			table.insert(tItems, { iTitle = tTitles[i], iLinks = tLinks[i], iPub = tDates[i] } )
		end	
					 
	tPubDate_idx = {} 
		for k, v in pairs( tItems ) do	tPubDate_idx[ v.iPub ] = k  end			
	tPubDate_Array = {}		
		for k, v in pairs(tPubDate_idx) do table.insert(tPubDate_Array, k) end
		table.sort(tPubDate_Array, function(a,b) return a > b end)
-->>   END of added stuff ------------------------------------------------<<

	-----------------------------------------------------------------------
	-- OUTPUT
	
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "'..iNumberOfItems..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "Text" "'..sFeedTitle..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "LeftMouseUpAction" "'..sFeedLink..'"')
    SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "ToolTipText" "'..sFeedLink..'"')

-->>  -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
--       SET LATEST FEED DATE/TIME
	if iTimedUpdate == 1 then 
		SKIN:Bang('!SetVariable "LatestPost'..iCurrentFeed..'" "'..tostring(tPubDate_Array[1])..'"') 
	end

	for i, v in ipairs(tPubDate_Array) do
		iPubdate = v
		recnum = tPubDate_idx[iPubdate]
		Record = tItems[recnum]		 
		SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem'..i..'" "Text" "'..Record.iTitle..'"')
		SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem'..i..'" "LeftMouseUpAction" "'..Record.iLinks..'"')
	end
-->>  END of added stuff ------------------------------------------------<<

	-----------------------------------------------------------------------
	-- FINISH ACTION
	
	if sFinishAction ~= '' then
		SKIN:Bang(sFinishAction)
	end
	
	return 'Success!'
end

---------------------------------------------------------------------
-- SWITCHERS

function SwitchToNext()
-->>  -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
iTimedUpdate = 0
-->>  END of added stuff ------------------------------------------------<<
   iCurrentFeed = iCurrentFeed%iNumberOfFeeds + 1
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
   Update()
end

function SwitchToPrevious()
-->>  -- Added for sorting BBC feeds / bulleting latest feeds ----------<<
iTimedUpdate = 0
-->>  END of added stuff ------------------------------------------------<<
   iCurrentFeed = iCurrentFeed - 1 + (iCurrentFeed ==1 and iNumberOfFeeds or 0)
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
   Update()
end

function FeedError(sErrorName, sErrorLink, sErrorDesc)
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "Text" "'..sErrorName..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "LeftMouseUpAction" "'..sErrorLink..'"')
    SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "ToolTipText" "'..sErrorLink..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem1" "Text" "'..sErrorDesc..'"')
end

;---------------------------------------------------------------------
; METADATA

[Metadata]
Name=Evereader (Universal Feed Reader Prototype)
Config=Evereader
Description=This skin displays the contents of any Atom or RSS feed. | Thanks to Jeffrey Morley for his help in conceiving and writing this skin and script.
Instructions=Set "NumberOfFeeds" as the number of feeds you would like to use. (Though the Lua script can handle an indefinite number of feeds, the skin itself only supports three.) Set Feed1MeasureName, Feed2MeasureName, and Feed3MeasureName as the section names of each WebParser measure.
Version=0.3
Tags=Reader | Feed | RSS | Atom | Lua
License=Creative Commons Attribution-Noncommercial-Share Alike 3.0
Variant=1
I'd really appreciate any help in getting this to work properly..
User avatar
Kaelri
Developer
Posts: 1721
Joined: July 25th, 2009, 4:47 am

Re: [Proof of Concept] Universal Feed Reader

Post by Kaelri »

Preliminary observations: you had a couple of typos in the Lua script, and somehow, the skin's Metadata section ended up in the script as well. I removed those, and also streamlined the formatting stuff by merging all the "for i = 1, iNumberOfItems" loops.

The method you're using to disable the update-checking stuff on manual updates looks like the right approach. I think the trick is just putting everything into the right conditionals. Gonna play with it some more.

EDIT: I think I've got it. Testing.

EDIT 2: Or not. Regrouping.
User avatar
Kaelri
Developer
Posts: 1721
Joined: July 25th, 2009, 4:47 am

Re: [Proof of Concept] Universal Feed Reader

Post by Kaelri »

I got it. :)

The trick to comparing dates was to use two tables: one that stores the present timestamp for each feed, and another that stores the timestamp from the previous update (starting at zero, such that all items show up as "new" when the skin is initialized). When the WebParser measures update with new items from the server, they run a FinishAction to copy values from the "present" table to the "past" table, and then run Update(), which uses the newly-archived timestamps to set your markers in the tTitles table. (For what it's worth, I would suggest creating a new table for each feed item with Boolean values for "new" and "not new," rather than modifying the title strings. Up to you, though.)

I tried a whole bunch of methods to make things happen in the right order, including a mad hour where I split the entire Update function into Input and Output functions and stuck things in between. But the final function is actually quite simple:

Code: Select all

function TimedUpdate(a)
	tPastUpdate[a] = tPresentUpdate[a]
	Update()
end
In the current version of the script, running TimedUpdate() is the only event in which the comparison timestamp will be updated, so clicking the arrows/buttons to switch feeds will not affect it. (Note that this does require the "NumberOfFeeds" value to be set as a script measure setting, rather than a variable, which means you won't be able to change the number of feeds dynamically. I can't imagine why you'd want to do that in any case, but there you have it.)

Skin:

Code: Select all

[Rainmeter]
Update=1000
MiddleMouseUpAction=!Refresh
BackgroundMode=2
SolidColor=0,0,0,2

[Variables]
FeedMeasureName1=MeasureFeed
FeedMeasureName2=MeasureFeed2
FeedMeasureName3=MeasureFeed3
CurrentFeed=1

WebParserSubstitute="<![CDATA[":"","]]>":"","/PRE>":"","PRE>":"","'s Facebook Notifications":""

;---------------------------------------------------------------------
; MEASURES

[MeasureFeed]
Measure=Plugin
Plugin=Plugins\WebParser.dll
UpdateRate=600
;URL=http://news.google.com/news?pz=1&jfkl=true&cf=all&ned=us&hl=en&topic=h&num=10&output=atom
URL=http://feeds.bbci.co.uk/news/rss.xml?edition=uk
RegExp=(?siU)(.*)$
DecodeCharacterReference=1
FinishAction=!Execute [!CommandMeasure "MeasureLuaScript" "TimedUpdate(1)"][!ShowMeterGroup Text]

[MeasureFeed2]
Measure=Plugin
Plugin=Plugins\WebParser.dll
UpdateRate=600
Url=http://feeds.bbci.co.uk/news/science_and_environment/rss.xml
RegExp=(?siU)(.*)$
DecodeCharacterReference=1
Substitute=#WebParserSubstitute#
FinishAction=!Execute [!CommandMeasure "MeasureLuaScript" "TimedUpdate(2)"][!ShowMeterGroup Text]

[MeasureFeed3]
Measure=Plugin
Plugin=Plugins\WebParser.dll
UpdateRate=600
Url=http://feeds.bbci.co.uk/news/technology/rss.xml
RegExp=(?siU)(.*)$
DecodeCharacterReference=1
Substitute=#WebParserSubstitute#
FinishAction=!Execute [!CommandMeasure "MeasureLuaScript" "TimedUpdate(3)"][!ShowMeterGroup Text]

[MeasureLuaScript]
Measure=Script
ScriptFile="#CURRENTPATH#Evereader.lua"
Disabled=1
; FeedMeasureName=MeasureFeed
MultipleFeeds=1
NumberOfFeeds=3
TimedUpdate=1

;----------------------------------------------
; BUTTONS

[Grabber1State]
Measure=CALC
Formula=#CurrentFeed#=1
DynamicVariables=1

[Grabber2State]
Measure=CALC
Formula=#CurrentFeed#=2
DynamicVariables=1

[Grabber3State]
Measure=CALC
Formula=#CurrentFeed#=3
DynamicVariables=1

;---------------------------------------------------------------------
; STYLES

[StyleText]
X=r
Y=R
W=300
H=20
FontColor=255,255,255
FontSize=10
FontFace=Calibri
StringStyle=ITALIC
StringEffect=Shadow
AntiAlias=1
ClipString=1
ToolTipWidth=300
DynamicVariables=1
Group=Text
Hidden=1

[GrabberStyle]
X=10R
Y=r
W=20
H=20
DynamicVariables=1

[GrabberStyle0]
SolidColor=255,255,255,64

[GrabberStyle1]
SolidColor=255,255,255,217

[ArrowStyle]
X=R
Y=r
FontColor=255,255,255, 64
FontSize=16
FontFace=Webdings
StringStyle=NORMAL
AntiAlias=1
ClipString=1

;---------------------------------------------------------------------
; METERS

;----------------------------------------------
; GROUP 1

[FeedTitle]
Meter=STRING
MeterStyle=StyleText
X=10
H=25
FontSize=15
StringStyle=BOLD
Text=#FeedTitle#
LeftMouseUpAction=#FeedLink#
ToolTipText=#FeedLink#

[FeedItem1]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle1#
LeftMouseUpAction=#ItemLink1#
ToolTipText=#ItemLink1#

[FeedItem2]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle2#
LeftMouseUpAction=#ItemLink2#
ToolTipText=#ItemLink2#

[FeedItem3]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle3#
LeftMouseUpAction=#ItemLink3#
ToolTipText=#ItemLink3#

[FeedItem4]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle4#
LeftMouseUpAction=#ItemLink4#
ToolTipText=#ItemLink4#

[FeedItem5]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle5#
LeftMouseUpAction=#ItemLink5#
ToolTipText=#ItemLink5#

[FeedItem6]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle6#
LeftMouseUpAction=#ItemLink6#
ToolTipText=#ItemLink6#

[FeedItem7]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle7#
LeftMouseUpAction=#ItemLink7#
ToolTipText=#ItemLink7#

[FeedItem8]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle8#
LeftMouseUpAction=#ItemLink8#
ToolTipText=#ItemLink8#

[FeedItem9]
Meter=STRING
MeterStyle=StyleText
Text=#ItemTitle9#
LeftMouseUpAction=#ItemLink9#
ToolTipText=#ItemLink9#

;-----------------------

[ArrowPrevious]
Meter=STRING
MeterStyle=ArrowStyle
X=r
Y=10R
Text=7
LeftMouseUpAction=!Execute [!CommandMeasure "MeasureLuaScript" "SwitchToPrevious()"][!Update]

[Grabber1]
Meter=IMAGE
MeterStyle=GrabberStyle | GrabberStyle[Grabber1State]
X=R
LeftMouseUpAction=!Execute [!SetVariable CurrentFeed 1][!CommandMeasure "MeasureLuaScript" "Update()"][!Update]

[Grabber2]
Meter=IMAGE
MeterStyle=GrabberStyle | GrabberStyle[Grabber2State]
LeftMouseUpAction=!Execute [!SetVariable CurrentFeed 2][!CommandMeasure "MeasureLuaScript" "Update()"][!Update]

[Grabber3]
Meter=IMAGE
MeterStyle=GrabberStyle | GrabberStyle[Grabber3State]
LeftMouseUpAction=!Execute [!SetVariable CurrentFeed 3][!CommandMeasure "MeasureLuaScript" "Update()"][!Update]

[ArrowNext]
Meter=STRING
MeterStyle=ArrowStyle
Text=8
LeftMouseUpAction=!Execute [!CommandMeasure "MeasureLuaScript" "SwitchToNext()"][!Update]

;---------------------------------------------------------------------
; METADATA

[Metadata]
Name=Evereader (Universal Feed Reader Prototype)
Config=Evereader
Description=This skin displays the contents of any Atom or RSS feed. | Thanks to Jeffrey Morley for his help in conceiving and writing this skin and script.
Instructions=Set "NumberOfFeeds" as the number of feeds you would like to use. (Though the Lua script can handle an indefinite number of feeds, the skin itself only supports three.) Set Feed1MeasureName, Feed2MeasureName, and Feed3MeasureName as the section names of each WebParser measure.
Version=0.2
Tags=Reader | Feed | RSS | Atom | Lua
License=Creative Commons Attribution-Noncommercial-Share Alike 3.0

Script:

Code: Select all

PROPERTIES = {
	FeedMeasureName = '';
	MultipleFeeds = 0;
	NumberOfFeeds = 1;
	VariablePrefix = '';
	MinItems = 0;
	SpecialFormat = '';
	FinishAction = '';
	TimedUpdate = 0;
}

-- When Rainmeter supports escape characters for bangs, use this function to escape quotes.
function ParseSpecialCharacters(sString)
	sString = string.gsub(sString, '&nbsp;', ' ')
	sString = string.gsub(sString, '\"', '')
	return sString
end

function Initialize()
	sFeedMeasureName = PROPERTIES.FeedMeasureName
	iMultipleFeeds = tonumber(PROPERTIES.MultipleFeeds)
	sVariablePrefix = PROPERTIES.VariablePrefix
	iMinItems = tonumber(PROPERTIES.MinItems)
	iNumberOfFeeds = tonumber(PROPERTIES.NumberOfFeeds)
	sFinishAction = PROPERTIES.FinishAction
	sSpecialFormat = PROPERTIES.SpecialFormat
	iTimedUpdate = tonumber(PROPERTIES.TimedUpdate)
	tFeeds = {}
	tTitles = {}
	tLinks = {}
	tDates = {}
	tmText = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
	tmNum = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}
	tPastUpdate = {}
	tPresentUpdate = {}
	for i = 1, iNumberOfFeeds do
		tPastUpdate[i] = 0;
		tPresentUpdate[i] = 0;
	end
end

function Update()

	-----------------------------------------------------------------------
	-- INPUT FEED(S)
	
	if iMultipleFeeds == 1 then
		for i = 1, iNumberOfFeeds do
			tFeeds[i] = SKIN:GetVariable(sVariablePrefix..'FeedMeasureName'..i)
		end
		iCurrentFeed = tonumber(SKIN:GetVariable(sVariablePrefix..'CurrentFeed'))
		msRaw = SKIN:GetMeasure(tFeeds[iCurrentFeed])
	else
		msRaw = SKIN:GetMeasure(sFeedMeasureName)
	end
	sRaw = msRaw:GetStringValue()
	
	-----------------------------------------------------------------------
	-- DETERMINE FEED FORMAT AND CONTENTS
	
	sPatternFeedTitle = '.-<title.->(.-)</title>'
	sPatternItemTitle = '.-<title.->(.-)</title>'
	if string.match(sRaw, 'xmlns:gCal') then
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
		sPatternFeedLink = '.-<link.-rel=.-alternate.-href=\'(.-)\''
		sPatternItem = '<entry.-</entry>'
		sPatternItemLink = '.-<link.-href=\'(.-)\''
		sPatternItemDate = '.-When: (.-)<'
	elseif string.match(sRaw, '<subtitle>rememberthemilk.com</subtitle>') then
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
		sPatternFeedLink = '.-<link.-rel=.-alternate.-href="(.-)"'
		sPatternItem = '<entry.-</entry>'
		sPatternItemLink = '.-<link.-href="(.-)"'
		sPatternItemDate = '<span class="rtm_due_value">(.-)</span>'
	elseif string.match(sRaw, '<rss.-version=".-".->') then
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<item', "")
		sPatternFeedLink = '.-<link.->(.-)</link>'
		sPatternItem = '<item.-</item>'
		sPatternItemLink = '.-<link.->(.-)</link>'
		sPatternItemDesc = '.-<description.->(.-)</description>'
		sPatternItemDate = '.-<pubDate.->(.-)</pubDate>'
	else
		sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
		sPatternFeedLink = '.-<link.-href="(.-)"'
		sPatternItem = '<entry.-</entry>'
		sPatternItemLink = '.-<link.-href="(.-)"'
		sPatternItemDesc = '.-<summary.->(.-)</summary>'
		sPatternItemDate = '.-<updated.->(.-)</updated>'
	end
	
	-----------------------------------------------------------------------
	-- ERRORS
	
	sFeedTitle, sFeedLink = string.match(sRaw, sPatternFeedTitle..sPatternFeedLink)
	if not sFeedTitle then
		FeedError('Error', '', 'Connection or matching error.')
		return 'Error: matching.'
	end
	
	sFeedTitle = ParseSpecialCharacters(sFeedTitle)
	sFeedLink = ParseSpecialCharacters(sFeedLink)
	
	if iNumberOfItems == 0 then
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "0"')
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sFeedTitle..'"')
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sFeedLink..'"')
		FeedError(sFeedTitle, '', 'Empty.')
		return 'Error: empty feed.'
	end
	
	-----------------------------------------------------------------------
	-- CREATE DATABASE
	
	local tItems = {}
	iInit = 0
	for i = 1, iNumberOfItems do
		iItemStart, iItemEnd = string.find(sRaw, sPatternItem, iInit)
		sItem = string.sub(sRaw, iItemStart, iItemEnd)
		tTitles[i] = string.match(sItem, sPatternItemTitle)
		tTitles[i] = ParseSpecialCharacters(tTitles[i])
		tLinks[i] = string.match(sItem, sPatternItemLink)
		tLinks[i] = ParseSpecialCharacters(tLinks[i])
		tDates[i] = string.match(sItem, sPatternItemDate)
		tDates[i] = ParseSpecialCharacters(tDates[i])
		for j = 1, 12 do tDates[i]  =  string.gsub(tDates[i], tmText[j] , tmNum[j] ) end     
		tDates[i]  =  string.sub(tDates[i],  12, 15)..string.sub(tDates[i],  9, 10)..string.sub(tDates[i],6, 7)..string.sub(tDates[i],17, 18)..string.sub(tDates[i],20,21)..string.sub(tDates[i],23,24)
		iInit = iItemEnd + 1
		if tonumber(tDates[i]) > tonumber(tPastUpdate[iCurrentFeed]) then
			tTitles[i] = "• "..tTitles[i] 
		else
			tTitles[i] = "  "..tTitles[i]
		end
		table.insert(tItems, { iTitle = tTitles[i], iLinks = tLinks[i], iPub = tDates[i] } )
	end
	
	-----------------------------------------------------------------------
	-- SORT DATABASE BY PUB DATE
                
	tPubDate_idx = {} 
	for k, v in pairs(tItems) do
		tPubDate_idx[ v.iPub ] = k
	end         
	tPubDate_Array = {}      
	for k, v in pairs(tPubDate_idx) do
		table.insert(tPubDate_Array, k)
	end
	table.sort(tPubDate_Array, function(a,b) return a > b end)
	tPresentUpdate[iCurrentFeed] = tonumber(tPubDate_Array[1])
	
	-----------------------------------------------------------------------
	-- OUTPUT
	
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "'..iNumberOfItems..'"')
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sFeedTitle..'"')
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sFeedLink..'"')
	
	for i, v in ipairs(tPubDate_Array) do
		iRecnum = tPubDate_idx[v]
		Record = tItems[iRecnum]       
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" "'..Record.iTitle..'"')
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" "'..Record.iLinks..'"')
	end

	if  iMinItems > iNumberOfItems then
		iFirstMissingItem = iNumberOfItems + 1
		for i = iFirstMissingItem, iMinItems do
			SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" ""')
			SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" ""')
			SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate'..i..'" ""')
		end
	end

	-----------------------------------------------------------------------
	-- FINISH ACTION
	
	if sFinishAction ~= '' then
		SKIN:Bang(sFinishAction)
	end
	
	return 'Success!'
end

---------------------------------------------------------------------
-- SWITCHERS

function TimedUpdate(a)
	tPastUpdate[a] = tPresentUpdate[a]
	Update()
end

function SwitchToNext()
	iCurrentFeed = iCurrentFeed % iNumberOfFeeds + 1
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
	Update()
end

function SwitchToPrevious()
	iCurrentFeed = iCurrentFeed - 1 + (iCurrentFeed == 1 and iNumberOfFeeds or 0)
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
	Update()
end

function SwitchToFeed(a)
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" '..a)
	Update()
end

function FeedError(sErrorName, sErrorLink, sErrorDesc)
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sErrorName..'"')
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sErrorLink..'"')
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle1" "'..sErrorDesc..'"')
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink1" ""')
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate1" ""')
	if  iMinItems > 1 then
		for i = 2, iMinItems do
			SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" ""')
			SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" ""')
			SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate'..i..'" ""')
		end
	end
end
User avatar
Mordasius
Posts: 1171
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: [Proof of Concept] Universal Feed Reader

Post by Mordasius »

Kaelri wrote:
I got it. :)

Yea me to! At almost exactly the same time as I saw your post flash up I managed to get my version working. I however used brute force rather than slinky programming to force the LUA script to do what I wanted it to (last time I spent any serious time programming was with Borland's Turbo Pascal back in the 1980s!). Basicaly, I added some PreviousLatest variables to store the previous last post and then put more in to the FinishAction of the [MeasureFeed]s and the LeftMouseUpAction of the Grabbers. A spot of tinkering in the LUA script and it works - most of the time.

Thanks very much for all your hard work, I'll have a good look through the script and try creating a new table for each feed item as you suggest. For what it's worth my solution was

In the Skin:

Code: Select all

[Variables]
NumberOfFeeds=3
FeedMeasureName1=MeasureFeed
FeedMeasureName2=MeasureFeed2
FeedMeasureName3=MeasureFeed3
CurrentFeed=1

List2.NumberOfFeeds=3
List2.FeedMeasureName1=MeasureFeed4
List2.FeedMeasureName2=MeasureFeed5
List2.FeedMeasureName3=MeasureFeed6
List2.CurrentFeed=1

PreviousLatest1=0
PreviousLatest2=0
PreviousLatest3=0
LatestPost1=1
LatestPost2=2
LatestPost3=3

WebParserSubstitute="<![CDATA[":"","]]>":"","/PRE>":"","PRE>":"","'s Facebook Notifications":""

;---------------------------------------------------------------------
; MEASURES
[MeasureFeed]
Measure=Plugin
Plugin=Plugins\WebParser.dll
UpdateRate=600
URL=http://feeds.bbci.co.uk/news/rss.xml?edition=uk
RegExp=(?siU)(.*)$
DecodeCharacterReference=1
Substitute=#WebParserSubstitute#
FinishAction=!Execute [!SetVariable "CurrentFeed" "1" ] [!CommandMeasure "MeasureLuaScript" "iTimedUpdate = 1"] [!CommandMeasure "MeasureLuaScript" "Update()"]  [!ShowMeterGroup Text]

[MeasureFeed2]
Measure=Plugin
Plugin=Plugins\WebParser.dll
UpdateRate=660
URL=http://feeds.bbci.co.uk/news/science_and_environment/rss.xml
RegExp=(?siU)(.*)$
DecodeCharacterReference=1
Substitute=#WebParserSubstitute#
FinishAction=!Execute [!SetVariable "CurrentFeed" "2" ] [!CommandMeasure "MeasureLuaScript" "iTimedUpdate = 1"] [!CommandMeasure "MeasureLuaScript" "Update()"] [!ShowMeterGroup Text]

[MeasureFeed3]
Measure=Plugin
Plugin=Plugins\WebParser.dll
UpdateRate=#UpdateRate3#
URL=http://feeds.bbci.co.uk/news/technology/rss.xml
RegExp=(?siU)(.*)$
DecodeCharacterReference=1
Substitute=#WebParserSubstitute#
FinishAction=!Execute [!SetVariable "CurrentFeed" "3" ] [!CommandMeasure "MeasureLuaScript" "iTimedUpdate = 1"] [!CommandMeasure "MeasureLuaScript" "Update()"] [!ShowMeterGroup Text]

[MeasureLuaScript]
Measure=Script
ScriptFile="#CURRENTPATH#Evereader3.lua"
Disabled=1
; FeedMeasureName=MeasureFeed
MultipleFeeds=1
TimedUpdate=0

...

[Grabber1]
Meter=IMAGE
MeterStyle=GrabberStyle | GrabberStyle[Grabber1State]
X=12R
LeftMouseUpAction=!Execute [!SetVariable CurrentFeed 1] [!CommandMeasure "MeasureLuaScript" "iTimedUpdate = 0"] [!CommandMeasure "MeasureLuaScript" "Update()"][!Update]

[Grabber2]
Meter=IMAGE
MeterStyle=GrabberStyle | GrabberStyle[Grabber2State]
LeftMouseUpAction=!Execute [!SetVariable CurrentFeed 2] [!CommandMeasure "MeasureLuaScript" "iTimedUpdate = 0"] [!CommandMeasure "MeasureLuaScript" "Update()"][!Update]

[Grabber3]
Meter=IMAGE
MeterStyle=GrabberStyle | GrabberStyle[Grabber3State]
LeftMouseUpAction=!Execute [!SetVariable CurrentFeed 3] [!CommandMeasure "MeasureLuaScript" "iTimedUpdate = 0"] [!CommandMeasure "MeasureLuaScript" "Update()"][!Update]
and the LUA script:

Code: Select all

PROPERTIES = {
   FeedMeasureName = '';
   MultipleFeeds = 0;
   VariablePrefix = '';
   MinItems = 0;
   SpecialFormat = '';
   FinishAction = '';
   TimedUpdate = 0;
}

-- When Rainmeter supports escape characters for bangs, use this function to escape quotes.
function ParseSpecialCharacters(sString)
   sString = string.gsub(sString, '&nbsp;', ' ')
   sString = string.gsub(sString, '\"', '')
   return sString
end

function Initialize()
   sFeedMeasureName = PROPERTIES.FeedMeasureName
   iMultipleFeeds = tonumber(PROPERTIES.MultipleFeeds)
   sVariablePrefix = PROPERTIES.VariablePrefix
   iMinItems = tonumber(PROPERTIES.MinItems)
   sFinishAction = PROPERTIES.FinishAction
   sSpecialFormat = PROPERTIES.SpecialFormat
   iTimedUpdate = tonumber(PROPERTIES.TimedUpdate)
   tFeeds = {}
   tTitles = {}
   tLinks = {}
   tDates = {}
   tmText = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
   tmNum = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}
end

function Update()

   -----------------------------------------------------------------------
   -- INPUT FEED(S)
   
   if iMultipleFeeds == 1 then
      iNumberOfFeeds = tonumber(SKIN:GetVariable(sVariablePrefix..'NumberOfFeeds'))
      for i = 1, iNumberOfFeeds do
         tFeeds[i] = SKIN:GetVariable(sVariablePrefix..'FeedMeasureName'..i)
      end
      iCurrentFeed = tonumber(SKIN:GetVariable(sVariablePrefix..'CurrentFeed'))
      msRaw = SKIN:GetMeasure(tFeeds[iCurrentFeed])
   else
      msRaw = SKIN:GetMeasure(sFeedMeasureName)
   end
   sRaw = msRaw:GetStringValue()
   
   -----------------------------------------------------------------------
   -- DETERMINE FEED FORMAT AND CONTENTS
   
   sPatternFeedTitle = '.-<title.->(.-)</title>'
   sPatternItemTitle = '.-<title.->(.-)</title>'
   if string.match(sRaw, 'xmlns:gCal') then
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
      sPatternFeedLink = '.-<link.-rel=.-alternate.-href=\'(.-)\''
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href=\'(.-)\''
      sPatternItemDate = '.-When: (.-)<'
   elseif string.match(sRaw, '<subtitle>rememberthemilk.com</subtitle>') then
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
      sPatternFeedLink = '.-<link.-rel=.-alternate.-href="(.-)"'
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href="(.-)"'
      sPatternItemDate = '<span class="rtm_due_value">(.-)</span>'
   elseif string.match(sRaw, '<rss.-version=".-".->') then
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<item', "")
      sPatternFeedLink = '.-<link.->(.-)</link>'
      sPatternItem = '<item.-</item>'
      sPatternItemLink = '.-<link.->(.-)</link>'
      sPatternItemDesc = '.-<description.->(.-)</description>'
      sPatternItemDate = '.-<pubDate.->(.-)</pubDate>'
   else
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
      sPatternFeedLink = '.-<link.-href="(.-)"'
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href="(.-)"'
      sPatternItemDesc = '.-<summary.->(.-)</summary>'
      sPatternItemDate = '.-<updated.->(.-)</updated>'
   end
   
   -----------------------------------------------------------------------
   -- ERRORS
   
   sFeedTitle, sFeedLink = string.match(sRaw, sPatternFeedTitle..sPatternFeedLink)
   if not sFeedTitle then
      FeedError('Error', '', 'Connection or matching error.')
      return 'Error: matching feed Title.'
   end
   
   sFeedTitle = ParseSpecialCharacters(sFeedTitle)
   sFeedLink = ParseSpecialCharacters(sFeedLink)
   
   if iNumberOfItems == 0 then
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "0"')
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sFeedTitle..'"')
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sFeedLink..'"')
      FeedError(sFeedTitle, '', 'Empty.')
      return 'Error: empty feed.'
   end
   
   -----------------------------------------------------------------------
   -- CREATE DATABASE
   
   if iTimedUpdate == 1 then
      sMostRecent = SKIN:GetVariable('LatestPost'..iCurrentFeed) 
   else
      sMostRecent = SKIN:GetVariable('PreviousLatest'..iCurrentFeed)   
   end   

   tItems = {}
   
   iInit = 0
   for i = 1, iNumberOfItems do
      iItemStart, iItemEnd = string.find(sRaw, sPatternItem, iInit)
      sItem = string.sub(sRaw, iItemStart, iItemEnd)
      tTitles[i] = string.match(sItem, sPatternItemTitle)
      tTitles[i] = ParseSpecialCharacters(tTitles[i])
      tLinks[i] = string.match(sItem, sPatternItemLink)
      tLinks[i] = ParseSpecialCharacters(tLinks[i])
      tDates[i] = string.match(sItem, sPatternItemDate)
      tDates[i] = ParseSpecialCharacters(tDates[i])
      for j = 1, 12 do tDates[i]  =  string.gsub(tDates[i], tmText[j] , tmNum[j] ) end     
      tDates[i]  =  string.sub(tDates[i],  12, 15)..string.sub(tDates[i],  9, 10)..string.sub(tDates[i],6, 7)..string.sub(tDates[i],17, 18)..string.sub(tDates[i],20,21)..string.sub(tDates[i],23,24)
      if tonumber(tDates[i]) > tonumber(sMostRecent) then
         tTitles[i] = "• "..tTitles[i]
      else
         tTitles[i] = "  "..tTitles[i]
      end
      table.insert(tItems, { iTitle = tTitles[i], iLinks = tLinks[i], iPub = tDates[i] } )
      iInit = iItemEnd + 1
   end
   
   -----------------------------------------------------------------------
   -- BBC STUFF
               
   tPubDate_idx = {}
   for k, v in pairs(tItems) do
      tPubDate_idx[ v.iPub ] = k
   end         
   tPubDate_Array = {}     
   for k, v in pairs(tPubDate_idx) do
      table.insert(tPubDate_Array, k)
   end
   table.sort(tPubDate_Array, function(a,b) return a > b end)
   
   -----------------------------------------------------------------------
   -- OUTPUT
   
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "'..iNumberOfItems..'"')
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sFeedTitle..'"')
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sFeedLink..'"')

   -- for i = 1, iNumberOfItems do
      -- SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" "'..tTitles[i]..'"')
      -- SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" "'..tLinks[i]..'"')
      -- SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate'..i..'" "'..tDates[i]..'"')
   -- end
   
   for i, v in ipairs(tPubDate_Array) do
      iPubdate = v
      sRecnum = tPubDate_idx[iPubdate]
      Record = tItems[sRecnum]       
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" "'..Record.iTitle..'"')
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" "'..Record.iLinks..'"')
   end

   if  iMinItems > iNumberOfItems then
      iFirstMissingItem = iNumberOfItems + 1
      for i = iFirstMissingItem, iMinItems do
         SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" ""')
         SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" ""')
         SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate'..i..'" ""')
      end
   end
   
   if iTimedUpdate == 1 then
      SKIN:Bang('!SetVariable "PreviousLatest'..iCurrentFeed..'" "'..sMostRecent..'"')
      SKIN:Bang('!SetVariable "LatestPost'..iCurrentFeed..'" "'..tostring(tPubDate_Array[1])..'"')
   end

   -----------------------------------------------------------------------
   -- FINISH ACTION
   
   if sFinishAction ~= '' then
      SKIN:Bang(sFinishAction)
   end
   
   return 'Success!'
end

---------------------------------------------------------------------
-- SWITCHERS

function SwitchToNext()
   iTimedUpdate = 0
   iCurrentFeed = iCurrentFeed % iNumberOfFeeds + 1
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
   Update()
end

function SwitchToPrevious()
   iTimedUpdate = 0
   iCurrentFeed = iCurrentFeed - 1 + (iCurrentFeed == 1 and iNumberOfFeeds or 0)
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
   Update()
end

-- function SwitchToFeed(a)
   -- iTimedUpdate = 0
   -- SKIN:Bang('!SetVariable CurrentFeed '..a)
   -- SNum=0
   -- Update()
-- end

function FeedError(sErrorName, sErrorLink, sErrorDesc)
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sErrorName..'"')
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sErrorLink..'"')
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle1" "'..sErrorDesc..'"')
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink1" ""')
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate1" ""')
   if  iMinItems > 1 then
      for i = 2, iMinItems do
         SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" ""')
         SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" ""')
         SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemDate'..i..'" ""')
      end
   end
end
Thanks again for your time and effort Kaelri - much appreciated!
Last edited by Mordasius on August 23rd, 2011, 12:17 pm, edited 1 time in total.
User avatar
Mordasius
Posts: 1171
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: [Proof of Concept] Universal Feed Reader

Post by Mordasius »

Kaelri wrote:For what it's worth, I would suggest creating a new table for each feed item with Boolean values for "new" and "not new," rather than modifying the title strings. Up to you, though.
Is the aim of this to make the LUA script more efficient by not changing the title string for 20-30 title strings that will never be displayed? Would the 'bullet' then be added with an if "new" clause to the SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" ""') statement?
User avatar
Kaelri
Developer
Posts: 1721
Joined: July 25th, 2009, 4:47 am

Re: [Proof of Concept] Universal Feed Reader

Post by Kaelri »

Mordasius wrote: Is the aim of this to make the LUA script more efficient by not changing the title string for 20-30 title strings that will never be displayed? Would the 'bullet' then be added with an if "new" clause to the SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" ""') statement?
I would do something like this, assuming you've put it into your "tItems" table in the same way as the others:

Code: Select all

	for i, v in ipairs(tPubDate_Array) do
		iRecnum = tPubDate_idx[v]
		Record = tItems[iRecnum]       
		if Record.iNew == 1 then
			sNewMarker = '• '
		else
			sNewMarker = '';
		end
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemTitle'..i..'" "'..Record.iTitle..'"')
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemLink'..i..'" "'..Record.iLinks..'"')
		SKIN:Bang('!SetVariable "'..sVariablePrefix..'ItemNewMarker'..i..'" "'..sNewMarker..'"')
	end
The principle is simply to ensure that script's 'input' and 'output' stages are firmly segregated, in order to minimize the chance that changing something in one stage will break all the others. Might not be necessary now, but it could really help you if you're trying to adapt the script to some other purpose in the future. (In this case, for example, inserting the marker in the Title values would prevent you from doing things like alphabetizing the results. And if you decide you want to indicate new items in a different way, such as changing the font color, it will a) be a lot easier to alter the bangs and b) won't require any changes to the format of the tItems table.)
User avatar
Mordasius
Posts: 1171
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: [Proof of Concept] Universal Feed Reader

Post by Mordasius »

Kaelri wrote: The principle is simply to ensure that script's 'input' and 'output' stages are firmly segregated, in order to minimize the chance that changing something in one stage will break all the others. Might not be necessary now, but it could really help you if you're trying to adapt the script to some other purpose in the future. (In this case, for example, inserting the marker in the Title values would prevent you from doing things like alphabetizing the results. And if you decide you want to indicate new items in a different way, such as changing the font color, it will a) be a lot easier to alter the bangs and b) won't require any changes to the format of the tItems table.)
Sounds good to me (and more importantly it's something I can see how to do using your example!)

Next things on the 2do list:
1) Add a next update bar or pie chart that shows how long before each feed is updated (I have a different #UpdateRate# for each feed depending on how often I need to see new items and sometimes get fed up waiting and so click middlemousebutton to update everything immediately).
2) Play a (subtle) sound if any new items appear when a feed is updated.
3) add something like an "if string.match(sRaw, 'Rainmeter Forums') then" statement to strip out the "forum topic" so I can see the "Post Subject" without most of it being cut by the clipstring ( ... ) in my 300 pixel wide skin window.

Thanks once again for your help Kaelri :)
User avatar
Mordasius
Posts: 1171
Joined: January 22nd, 2011, 4:23 pm
Location: GMT +8

Re: [Proof of Concept] Universal Feed Reader

Post by Mordasius »

Finally got around to making time to catch up on the above:

1) Add a next update bar or pie chart that shows how long before each feed is updated
Decided this was a bit of an overkill and that a simple system time/date stamp showing when the feed items were last updated was much more useful.

2) Play a (subtle) sound if any new items appear when a feed is updated.
Quite fun playing around trying to make it work but it proved to be really annoying so I abandoned all audio alerts for the updates (bullet marks are enough thank you).

3) add something like an "if string.match(sRaw, 'Rainmeter Forums') then" statement to strip out the "forum topic" so I can see the "Post Subject" without most of it being cut by the clipstring
Implemented this by adding another elseif clause in the LUA script to handle the Rainmeter Forums.

UPDATE: 02 September - Having problems including the Rainmeter Forums as the script seems to cause rainmeter to crash if there is code in a post that includes part of a RegExp with things like <entry or <title in it. The dates are also in a different format to most other feeds (e.g. <published>2011-09-02T09:10:45+08:00</published> vs <pubDate>Fri, 02 Sep 2011 19:11:39 GMT</pubDate> which means it has to be rearranged with a separate set of string.subs before sorting. - I'm now thinking it best to go back to a separate skin for the Rainmeter Forums.

UPDATE: 04 September Found an error in the sorting of entries by date which meant that an entry with exactly the same date/time as another entery would be dropped. That is now fixed (actually less code than before) and I've added a few tweaks to the bulleting and included adjustments for the Rainmeter Forums so that the LUA script is now as follows:

Code: Select all

PROPERTIES = {
   FeedMeasureName = '';
   MultipleFeeds = 0;
   NumberOfFeeds = 1;
   VariablePrefix = '';
   MinItems = 0;
   FinishAction = '';
   TimedUpdate = 0;
}

-- When Rainmeter supports escape characters for bangs, use this function to escape quotes.
function ParseSpecialCharacters(sString)
   sString = string.gsub(sString, '&nbsp;', ' ')
   sString = string.gsub(sString, '\"', '')
   return sString
end

function Initialize()
   sFeedMeasureName = PROPERTIES.FeedMeasureName
   iMultipleFeeds = tonumber(PROPERTIES.MultipleFeeds)
   sVariablePrefix = PROPERTIES.VariablePrefix
   iMinItems = tonumber(PROPERTIES.MinItems)
   iNumberOfFeeds = tonumber(PROPERTIES.NumberOfFeeds)
   sFinishAction = PROPERTIES.FinishAction
   iTimedUpdate = tonumber(PROPERTIES.TimedUpdate)
   tFeeds = {}
   tTitles = {}
   tLinks = {}
   tDates = {}
   tNew = {}
   tmText = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
   tmNum = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}
   tPastUpdate = {}
   tPresentUpdate = {}
   for i = 1, iNumberOfFeeds do
      tPastUpdate[i] = 0;
      tPresentUpdate[i] = 0;
   end
   iTimedUpdate = 0
end

function Update()
   -----------------------------------------------------------------------
   -- INPUT FEED(S)
   
   if iMultipleFeeds == 1 then
      for i = 1, iNumberOfFeeds do
         tFeeds[i] = SKIN:GetVariable(sVariablePrefix..'FeedMeasureName'..i)
      end
      iCurrentFeed = tonumber(SKIN:GetVariable(sVariablePrefix..'CurrentFeed'))
      msRaw = SKIN:GetMeasure(tFeeds[iCurrentFeed])
   else
      msRaw = SKIN:GetMeasure(sFeedMeasureName)
   end
   sRaw = msRaw:GetStringValue()

   -----------------------------------------------------------------------
   -- DETERMINE FEED FORMAT AND CONTENTS
   iRainmeterForum = 0
   sPatternFeedTitle = '.-<title.->(.-)</title>'
   sPatternItemTitle = '.-<title.->(.-)</title>'
   if string.match(sRaw, 'xmlns:gCal') then
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
      sPatternFeedLink = '.-<link.-rel=.-alternate.-href=\'(.-)\''
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href=\'(.-)\''
      sPatternItemDate = '.-When: (.-)<'
   elseif string.match(sRaw, '<subtitle>rememberthemilk.com</subtitle>') then
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
      sPatternFeedLink = '.-<link.-rel=.-alternate.-href="(.-)"'
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href="(.-)"'
      sPatternItemDate = '<span class="rtm_due_value">(.-)</span>'
   elseif string.match(sRaw, '<rss.-version=".-".->') then
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<item', "")
      sPatternFeedLink = '.-<link.->(.-)</link>'
      sPatternItem = '<item.-</item>'
      sPatternItemLink = '.-<link.->(.-)</link>'
      sPatternItemDesc = '.-<description.->(.-)</description>'
      sPatternItemDate = '.-<pubDate.->(.-)</pubDate>'	
   elseif string.match(sRaw, '<title>Rainmeter') then
 --This added for the Rainmeter Forums   	  
      iRainmeterForum = 1
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
	  sPatternItemTitle = '.-<title.-• (.-)</title>'
      sPatternFeedLink = '.-<link.-href="(.-)".-/>'
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href="(.-)"'
      sPatternItemDate = '.-<published>(.-)</published>'
   else
      sRawCounted, iNumberOfItems = string.gsub(sRaw, '<entry', "")
      sPatternFeedLink = '.-<link.-href="(.-)"'
      sPatternItem = '<entry.-</entry>'
      sPatternItemLink = '.-<link.-href="(.-)"'
      sPatternItemDesc = '.-<summary.->(.-)</summary>'
      sPatternItemDate = '.-<updated.->(.-)</updated>'
   end
   
   -----------------------------------------------------------------------
   -- ERRORS
   
   sFeedTitle, sFeedLink = string.match(sRaw, sPatternFeedTitle..sPatternFeedLink)
   if not sFeedTitle then
      FeedError('Error', '', 'Connection or matching error.')
      return 'Error: matching.'
   end
   
   sFeedTitle = ParseSpecialCharacters(sFeedTitle)
   sFeedLink = ParseSpecialCharacters(sFeedLink)
   
   if iNumberOfItems == 0 then
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "0"')
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedTitle" "'..sFeedTitle..'"')
      SKIN:Bang('!SetVariable "'..sVariablePrefix..'FeedLink" "'..sFeedLink..'"')
      FeedError(sFeedTitle, '', 'Empty.')
      return 'Error: empty feed.'
   end
   
   -----------------------------------------------------------------------
   -- CREATE DATABASE
   
   local tItems = {}
   iInit = 0
   for i = 1, iNumberOfItems do
      iItemStart, iItemEnd = string.find(sRaw, sPatternItem, iInit)
      sItem = string.sub(sRaw, iItemStart, iItemEnd)
      tTitles[i] = string.match(sItem, sPatternItemTitle)
      tTitles[i] = ParseSpecialCharacters(tTitles[i])
      tLinks[i] = string.match(sItem, sPatternItemLink)
      tLinks[i] = ParseSpecialCharacters(tLinks[i])
      tDates[i] = string.match(sItem, sPatternItemDate)
      tDates[i] = ParseSpecialCharacters(tDates[i])	  
		if iRainmeterForum == 1 then
	        tDates[i]  =  string.sub(tDates[i],  1, 4)..string.sub(tDates[i],  6, 7)..string.sub(tDates[i],9, 10)..string.sub(tDates[i],12, 13)..string.sub(tDates[i],15,16)..string.sub(tDates[i],18, 19)
		else 		   
                for j = 1, 12 do tDates[i]  =  string.gsub(tDates[i], tmText[j] , tmNum[j] ) end 	   
                tDates[i]  =  string.sub(tDates[i],  12, 15)..string.sub(tDates[i],  9, 10)..string.sub(tDates[i],6, 7)..string.sub(tDates[i],17, 18)..string.sub(tDates[i],20,21)..string.sub(tDates[i],23,24)
		end		
      iInit = iItemEnd + 1	  
      tNew[i] = (tonumber(tDates[i]) > tonumber(tPastUpdate[iCurrentFeed]) )
      table.insert(tItems, { Title = tTitles[i], Links = tLinks[i], PubDate = tDates[i], NewOne = tNew[i] } )
   end
   -----------------------------------------------------------------------
   -- SORT DATABASE BY PUB DATE -- needed for BBC and Guardian News feeds
	ItemsByDate = {}
	for k,v in pairs( tItems ) do
		  table.insert( ItemsByDate, {key=k, record=v} )
	end
	table.sort(ItemsByDate, function(a,b) return a.record.PubDate > b.record.PubDate end)
	tPresentUpdate[iCurrentFeed] = tonumber( ItemsByDate[1].record.PubDate) 
   -----------------------------------------------------------------------
   -- OUTPUT
	
	SKIN:Bang('!SetVariable "'..sVariablePrefix..'NumberOfItems" "'..iNumberOfItems..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "Text" "'..sFeedTitle..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "LeftMouseUpAction" "'..sFeedLink..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedIconImage" "ImageName" "Ico'..iCurrentFeed..'.png'..'"')

		for i, v in ipairs(ItemsByDate) do
			if v.record.NewOne then
				sNewBullet = '• '
			else
				sNewBullet = '  '
		    end
		SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem'..i..'" "LeftMouseUpAction" "'..v.record.Links..'"')
		SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem'..i..'" "Text" "'..sNewBullet..v.record.Title..'"')	
	    end
	
	   if  iMinItems > iNumberOfItems then
		  iFirstMissingItem = iNumberOfItems + 1
		  for i = iFirstMissingItem, iMinItems do
			SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem'..i..'" "LeftMouseUpAction" ""')
			SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem'..i..'" "Text" ""')
		  end
	   end

   -----------------------------------------------------------------------
   -- FINISH ACTION
   
   if sFinishAction ~= '' then
      SKIN:Bang(sFinishAction)
   end 
   return 'Success at last!'
end

---------------------------------------------------------------------
-- SWITCHERS

function TimedUpdate(a)
   tPastUpdate[a] = tPresentUpdate[a]
   iTimedUpdate = 1
   Update()
end

function SwitchToNext()
   iCurrentFeed = iCurrentFeed % iNumberOfFeeds + 1
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
   Update()
end

function SwitchToPrevious()
   iCurrentFeed = iCurrentFeed - 1 + (iCurrentFeed == 1 and iNumberOfFeeds or 0)
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" "'..iCurrentFeed..'"')
   Update()
end

function SwitchToFeed(a)
   SKIN:Bang('!SetVariable "'..sVariablePrefix..'CurrentFeed" '..a)
   Update()
end

function FeedError(sErrorName, sErrorLink, sErrorDesc)
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "Text" "'..sErrorName..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedTitle" "LeftMouseUpAction" "'..sErrorLink..'"')
	SKIN:Bang('!SetOption "'..sVariablePrefix..'FeedItem1" "Text" "'..sErrorDesc..'"')
end  
Mike1970
Posts: 10
Joined: June 25th, 2011, 1:34 pm
Location: Germany

Re: [Proof of Concept] Universal Feed Reader

Post by Mike1970 »

Thank you very much for this reader. Its great and even I can now add new feeds, thank you for that.

Is there a way to change the textcolor of a visited feed link?

I would suppose it would be in the FeedItem Section with LeftMouseUpAction and needs a bang?

I have added a second style named [StyleText2] a variable named TextStyle=StyleText which gets the normal style. When I click a feed link I want the textcolor to change. (right now it is to change the total style, but color would be enough actually)


[FeedItem1]
Meter=STRING
MeterStyle=#TextStyle#
Text=#ItemTitle1#
;LeftMouseUpAction=#ItemLink1#
LeftMouseUpAction=!Execute [#ItemLink1#] [!SetVariable #TextStyle# StyleText2]
ToolTipText=#ItemLink1#
DynamicVariables=1

Not sure if I got the bang right there, because obviously nothing happens :-( I suppose I haven't got the syntax for !SetVariable right. Another thought would be what happens on the next normal refresh of the skin? I'd see with this, the style gets set back to the normal one right?

Thanks for any input :)

edit: Scratch what I wrote :o Didn't think about it properly as it occured to me that the color has to be transferred to the next feed if new items pop up :handtohead: which is something way above my understanding of this. Please apologize for posting before using my brains, lol.
User avatar
Kaelri
Developer
Posts: 1721
Joined: July 25th, 2009, 4:47 am

Re: [Proof of Concept] Universal Feed Reader

Post by Kaelri »

Yeah, for what you want to accomplish, you'd need to modify the Lua script to store a list of visited links - basically, the Rainmeter equivalent of a browser history. You'd then need to check each item's link against the "visited" list, and set a new variable for each item, e.g. "Item1Visited," as true or false. You could then use that variable to determine the meter style for each item. (Or use !SetOption to change the style directly from Lua.)

This would actually not be too hard to implement, although if you want the list of visited links to persist between sessions (i.e. not be wiped by refreshing or restarting Rainmeter), you'll need to write them to a file, which could slow down performance a little bit. Still, it's a very doable project. :)

By the way, here's the problem with the bang you were trying to send:

Code: Select all

!SetVariable #TextStyle# StyleText2
Because you included the ## characters around TextStyle, you weren't changing the value of TextStyle, but rather setting a new variable which would be named whatever the current value of TextStyle is. If you use this -

Code: Select all

!SetVariable TextStyle StyleText2


- it should work fine.