使用 Microsoft PowerShell 从 RSS 阅读器导出选定项目





5.00/5 (2投票s)
The Old Reader 和 Inoreader 的 API 的示例用法
引言
您想导出 RSS 阅读器中的收藏项目吗?通过阅读器的 API,这项任务可以轻松自动化。
背景
很久以前,有 Google RSS Reader,但其管理层决定将其关闭。幸运的是,在其关闭后,The Old Reader 等服务仍然存在。
作为其继承者,它们喜欢沿用其 API 风格,因此您会在某些标识符中找到包含“google
”的 string
。
本文假定其读者是上述 RSS 服务器的活跃用户:拥有自己的账户,并且了解 RSS 的含义或如何在用户界面中标记项目。
PowerShell 入门介绍
首先说明一下:我是一名 C++ 程序员,主要有 Windows 经验。因此,阅读类 C 的计算语言对我来说没有问题,有时甚至会用 C# 写点东西。
但是,由于家里没有安装编译器,因此在私人使用中,我开始用 PowerShell 编写脚本。您只需要 Microsoft Windows(本文示例需要 PowerShell 3.0 或更高版本,Windows 7 及更高版本均可用)和任何简单的文本编辑器(记事本或 PowerShell 的集成脚本环境)。
好吧,更确切地说,任何 PowerShell 也可在 Microsoft Azure 的 Functions(Functions 是 Amazon Web Services Lambda 的替代品)中使用,并且应该已经支持 Linux,但我从未在那里测试过它的极限。
请先检查您的系统。按 Win+R,输入“powershell”(不带引号),然后按 Enter(或单击“确定”按钮)。如果一切顺利,您将看到 Windows PowerShell 控制台,并带有闪烁的光标,准备好输入。
输入 'dir
' 并按 Enter。您将看到与系统原生 dir 命令非常相似的输出。
现在输入 'cd ..
' 并按 Enter,我希望您开始感觉宾至如归。
自然而然地会尝试使用 'ver
',这会带来第一次失望,无论如何,试试看结果。
要显示 PowerShell 的版本,您需要调用 '$PSVersionTable
'(可以通过鼠标右键单击从剪贴板粘贴)。
如您所见,$PSVersionTable
是一个结构。我们需要的是它的 PSVersion
。也可以尝试 '$PSVersionTable.PSVersion
',您将看到更多细节。我们真正需要的是
'$PSVersionTable.PSVersion.Major
',在我们的例子中,它必须是 3 或更高,否则您将需要下载并安装,可能首先需要更新的 .NET Framework 版本(稍后,我们将调用 Now.ToUnixTimeSeconds()
,它需要 .NET 4.6 或更高版本)。
在启动了所需的 PowerShell 后,就可以进行经典的 Hello world!
示例了,在 PowerShell 中是 'write-output "Hello world!"
'。也尝试一下 'write-output 1+1
' 和 'write-output (1+1)
',看看后面还有更多内容。
运行 'exit
',您的第一个 PowerShell 课程就结束了。
现在您只需要知道,您可以将 PowerShell 命令写入文本文件(通常扩展名为 ps1),然后将它们作为脚本运行。
默认情况下,Microsoft 决定允许 'PowerShell.exe -File myscript.ps1
' 是过于危险的。为避免 UnauthorizedAccess
问题,只需使用 'PowerShell.exe -ExecutionPolicy Bypass -File myscript.ps1
'。我有点担心有一天任何危险代码都能找到并理解这段话。
从 The Old Reader 导出
这是 PowerShell 脚本,它从 The Old Reader 的已点赞项目列表中读取七天内的项目。您只需输入您的真实 The Old Reader 账户 ID($Email
)和密码($Passwd
),并为您的客户端选择自己的名称($ClientName
),以便 The Old Reader 的统计数据能够识别它。
脚本的主要结构很简单
- 使用前面提到的参数,进行客户端登录(
$ClientLoginURL
) - 使用收到的登录认证代码(
$ClientLoginAuth
),请求最近($LastNDays
)的已点赞项目列表($LikesURL
) - 对于每个项目,获取其参数(
$LikeURL
),将其标题($LikeTitle
)和 URL($LikeHref
)写入输出文件($OutFileName
),并定义文件头($LikeBeginString
)和文件尾($LikeEndString
)。
如果您想尝试 API 的输出,可以将 URL 直接输入到您的浏览器中。只需以传统方式登录服务器,并借助 cookie,您就不需要处理认证代码。
技术说明
如果您的电子邮件(或密码)包含加号 (+) 字符,则在 URL 中必须将其替换为 %2B
。
我将 $LikeBeginString
和 $LikeEndString
的内容保留为包含一些非英文字符编码的示例(输出将包含“Decembrové cítanie
”或“Tibor Blažko
”),PowerShell 的换行符(`n
)和引号(脚本中的双 ""
变为 "
)。
脚本使用 invoke-webrequest cmdlet。这意味着它需要 PowerShell 3.0 或更高版本(已在 4.0 版本上测试过)。
Web 请求的结果是 Microsoft.PowerShell.Commands.HtmlWebResponseObject,它有一个 Content
属性。请注意,对于非英文字符,其 ToString()
方法的输出应与 Content
不同。
为了正确获取 The Old Reader 返回的项目参数的 Unicode 编码,使用了 [Regex]::Unescape。如果您想查看前后差异,请在脚本中添加任何 'write-output $LikeString
'。
脚本始终请求服务器以 JSON 格式输出(output=json
),因为它比替代的 XML(atom)更简单。甚至可以使用任何高级 JSON(或 XML)解析器,但由于简单性,此脚本仅使用简单的 string
搜索,假设其格式永远不会改变。
此外,脚本的错误处理也很少。例如,对于成功的登录,输出包含 "Auth=" substring
就足够了。
除了与服务器通信之外,脚本还包含(非强制性)部分,可以改进其输出。
第一个部分(参见 #if
href 会被重定向)将待重定向的链接(例如 http://feedproxy.google.com/~r/chinadigitaltimes/bKzO/~3/FIifoUE0ihM/)更改为重定向后的链接(http://chinadigitaltimes.net/2016/12/translation-game-developers-lament-censors-demands/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+chinadigitaltimes%2FbKzO+%28China+Digital+Times+%28CDT%29%29)。
第二个部分(参见 #remove
一些统计参数)通过预定义已知情况的列表,删除链接中不必要的片段,因此我们的示例链接更改为 http://chinadigitaltimes.net/2016/12/translation-game-developers-lament-censors-demands/。
有些 Web 服务器在访问其内容时需要登录。对脚本而言,这意味着有效链接可能被重定向到登录页面,并且 Web 请求将以访问错误结束。因为脚本的请求包含 '-ErrorAction Ignore
',所以该操作只会以几行红色的控制台输出结束,并保留原始的、未重定向的链接。
当服务器返回的项目列表($LikesString
)包含 'continuation
' 时,表示尚未列出所有必需的项目。脚本仅显示 WARNING
,不会进一步迭代,希望在现实生活中,7 天内很难超过 1000 个项目(n=1000)。
#like.ps1
$Email = "tibor%2Btheoldreader@gmail.com";
$Passwd = "12345";
$LastNDays = 7;
$OutFileName = "LikesList.txt"
$ClientName = "tblazkoWordpressComExport";
$ClientLoginURL = "https://theoldreader.com/reader/api/0/accounts/ClientLogin?client=" +
$ClientName + "&accountType=HOSTED_OR_GOOGLE&service=reader&Email=" + $Email + "&Passwd=" + $Passwd;
$LikesURL = "https://theoldreader.com/reader/api/0/stream/items/ids?output=json&s=user/-/
state/com.google/like&n=1000&ot=";
$LikeURL = "https://theoldreader.com/reader/api/0/stream/items/contents?output=json&i=";
$UnescapeLikeString = 1;
#login to get authentication code necessary for the next requests
$ClientLogin = Invoke-WebRequest -Uri $ClientLoginURL -Method post;
$ClientLoginString = $ClientLogin.Content;
$ClientLoginAuthIndex = $ClientLoginString.IndexOf("Auth=", 0);
if($ClientLoginAuthIndex -lt 0)
{
#something failed
write-Output $ClientLoginString;
}
else
{
#this is authentication code
$ClientLoginAuth = $ClientLoginString.Substring($ClientLoginAuthIndex + 5);
$ClientLoginAuth = "GoogleLogin auth=" +
$ClientLoginAuth.Substring(0, $ClientLoginAuth.IndexOf(10));
#ask for list of liked items ids younger than $LastNDays
$LastNDaysUnixTimeSeconds = [DateTimeOffset]::Now.ToUnixTimeSeconds() - $LastNDays*24*60*60;
$LikesURL = $LikesURL + $LastNDaysUnixTimeSeconds.ToString();
$Likes = Invoke-WebRequest -Uri $LikesURL
-Method get -Headers @{"Authorization" = $ClientLoginAuth};
$LikesString = $Likes.Content;
if($LikesString.IndexOf("""itemRefs"":[", 0) -lt 0)
{
#something failed
write-Output $LikesString;
}
else
{
#write output file header
$LikeBeginString = "Decembrov$([char]0xe9)
$([char]0x10d)$([char]0xed)tanie`nEnglish needed`n<!--more-->`n"
Out-File -FilePath $OutFileName -InputObject $LikeBeginString;
#find item ids in the list
for($LikeIdIndex = $LikesString.IndexOf
("""id"":""", 0);$LikeIdIndex -gt 0;$LikeIdIndex = $LikesString.IndexOf("""id"":""", 0))
{
$LikesString = $LikesString.Substring($LikeIdIndex + 6);
$LikeId = $LikesString.Substring
(0, $LikesString.IndexOf("""", 0));
#get item contents
$LikeIdURL = $LikeURL + $LikeId;
$Like = Invoke-WebRequest -Uri $LikeIdURL
-Method get -Headers @{"Authorization" = $ClientLoginAuth};
$LikeString = $Like.Content;
if($UnescapeLikeString)
{
$LikeString = [Regex]::Unescape($LikeString);
}
#find item's title
$LikeTitleBegin = $LikeString.IndexOf("],
""title"":""", 0) + 11;
$LikeTitleEnd = $LikeString.IndexOf
(""",""published"":", $LikeTitleBegin);
$LikeTitle = $LikeString.Substring($LikeTitleBegin, $LikeTitleEnd - $LikeTitleBegin);
#write item's title to console and output file
write-Output $LikeTitle;
Out-File -FilePath $OutFileName -Append -InputObject $LikeTitle;
#find item's href
$LikeHrefBegin = $LikeString.IndexOf("""canonical"":
[{""href"":""", 0) + 22;
$LikeHrefEnd = $LikeString.IndexOf("""}],", $LikeHrefBegin);
$LikeHref = $LikeString.Substring($LikeHrefBegin, $LikeHrefEnd - $LikeHrefBegin);
if($LikeHref.IndexOf("ft.com/", 0) -lt 0)
{
#if href is to be redirected switch to redirected one
#note that this request sometimes fails f.e.
#because of access rights but we can ignore it
$Redirection = Invoke-WebRequest -Uri $LikeHref
-MaximumRedirection 0 -ErrorAction Ignore
if($Redirection.StatusCode -ge 300 -and $Redirection.StatusCode -lt 400)
{
$LikeHref = $Redirection.Headers.Location;
}
}
#remove some statistics parameters
if($LikeHref.IndexOf("economist.com/", 0) -gt 0)
{
$EconomistEnd = $LikeHref.IndexOf("?fsrc=rss", 0);
if($EconomistEnd -gt 0)
{
$LikeHref = $LikeHref.Substring(0, $EconomistEnd);
}
}
if($LikeHref.IndexOf("blogs.wsj.com/", 0) -gt 0)
{
$WsjEnd = $LikeHref.IndexOf("?mod=WSJBlog", 0);
if($WsjEnd -gt 0)
{
$LikeHref = $LikeHref.Substring(0, $WsjEnd);
}
}
if($LikeHref.IndexOf("chinadigitaltimes.net/", 0) -gt 0)
{
$CdtEnd = $LikeHref.IndexOf("?utm_source=feedburner", 0);
if($CdtEnd -gt 0)
{
$LikeHref = $LikeHref.Substring(0, $CdtEnd);
}
}
if($LikeHref.IndexOf("sinopsis.cz/", 0) -gt 0)
{
$SczEnd = $LikeHref.IndexOf("?utm_source=rssfeed", 0);
if($SczEnd -gt 0)
{
$LikeHref = $LikeHref.Substring(0, $SczEnd);
}
}
#write item's href to console and output file
write-Output $LikeHref;
$LikeHrefFull = "<a href=""" +
$LikeHref + """>" + $LikeHref + "</a>`n";
Out-File -FilePath $OutFileName -Append -InputObject $LikeHrefFull;
}
$ContinuationIdIndex = $LikesString.IndexOf("""continuation"":""", 0);
if($ContinuationIdIndex -gt 0)
{
$NotAllString = "WARNING: Not all items are listed!!!";
write-Output $NotAllString;
Out-File -FilePath $OutFileName -Append -InputObject $NotAllString;
}
#write output file footer
$LikeEndString = "Zozbieral: Tibor Bla$([char]0x17e)ko`nKv$([char]0x20f)
li spamu je diskusia uzavret$([char]0xe1). Pr$([char]0xed)spevky posielajte cez
<a href=""http://www.paypal.com"">tblazko@gmail.com</a>."
Out-File -FilePath $OutFileName -Append -InputObject $LikeEndString;
}
}
The Old Reader 的 API 文档可在 此处获取。
从 Inoreader 导出
用于 Inoreader
的脚本几乎相同,只需将开头替换为
#like.ps1
$Email = "tibor%2Binoreader@gmail.com";
$Passwd = "12345";
$LastNDays = 7;
$OutFileName = "LikesList.txt"
$AppId = "1000000001"
$AppKey = "12345678890abcdefghijklmnopqrtuv";
$AppIdKey = "AppId=" + $AppId + "&AppKey=" + $AppKey;
$ClientLoginURL = "https://www.inoreader.com/accounts/ClientLogin?" +
$AppIdKey + "&Email=" + $Email + "&Passwd=" + $Passwd;
$LikesURL = "https://www.inoreader.com/reader/api/0/stream/items/ids?" +
$AppIdKey + "&output=json&s=user/-/state/com.google/starred&ot="
$LikeURL = "https://www.inoreader.com/reader/api/0/stream/items/contents?" +
$AppIdKey + "&output=json&i=";
$UnescapeLikeString = 1;
一个很大的区别是 Inoreader 服务器的脚本标识,在这种情况下,是通过 AppId
和 AppKey
。您可以在 Inoreader 的 Web 界面中通过“首选项/开发者/创建新应用程序”来创建它们。
可以将脚本标识从 URL 移到请求的标头中,但为了简单起见,我将其保留在 URL 中。
由于 Inoreader 不支持“已点赞”项目,因此脚本显示“已加星标”的项目(参见 $LikesURL
)。The Old Reader 支持两者,但与 Inoreader 相比,它在 Web 界面中需要多点击一次。
Inoreader 的 API 文档可在 此处获取。