65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2016 年 12 月 26 日

CPOL

6分钟阅读

viewsIcon

17729

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 的统计数据能够识别它。

脚本的主要结构很简单

  1. 使用前面提到的参数,进行客户端登录($ClientLoginURL
  2. 使用收到的登录认证代码($ClientLoginAuth),请求最近($LastNDays)的已点赞项目列表($LikesURL
  3. 对于每个项目,获取其参数($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 服务器的脚本标识,在这种情况下,是通过 AppIdAppKey。您可以在 Inoreader 的 Web 界面中通过“首选项/开发者/创建新应用程序”来创建它们。
可以将脚本标识从 URL 移到请求的标头中,但为了简单起见,我将其保留在 URL 中。

由于 Inoreader 不支持“已点赞”项目,因此脚本显示“已加星标”的项目(参见 $LikesURL)。The Old Reader 支持两者,但与 Inoreader 相比,它在 Web 界面中需要多点击一次。

Inoreader 的 API 文档可在 此处获取。

© . All rights reserved.