C# Google 书签类






4.75/5 (32投票s)
一篇关于如何在您的应用程序中实现 Google 书签的文章

引言
Google 有许多当前没有 API 的可用服务。我的目标是为这些服务创建 API,第一个就是 Google 书签。经过数小时截获 HTTP 请求,我终于创建了一个能够检索、添加和修改给定帐户书签的类。迄今为止,我除了 Firefox 扩展外,还没有找到任何可以利用 Google 书签服务的代码。希望您觉得这个有用。我对此类有一些非常有趣的想法,敬请关注更多文章。
使用该类
实现此控件只需 5 步,如果只想读取项目,则为 4 步。
- 将
GoogleBookmarks
类添加到您的项目中 - 声明该类的实例
- 定义一个函数来处理
BookmarksRefreshed
事件 - 使用用户名和密码初始化该类
- 与书签交互
声明一个实例
由于我们将为此分配一个事件处理程序,您需要将其声明为全局变量
GBDemoClass.GoogleBookmarks GBookmarks = new GoogleBookmarks();
在 Form 的 Load
事件中定义一个事件处理程序
private void Form1_Load(object sender, EventArgs e)
{
GBookmarks.BookmarksRefreshed += new EventHandler(GBookmarks_BookmarksRefreshed);}
}
处理事件
在事件处理程序中,唯一传递的变量是 e.Username
。如果您使用多个类实例来处理多个帐户,这将非常有用,如果您共享一个处理程序,您就知道哪个帐户正在刷新。我认为,如果以某种方式将书签作为参数提供,会更简单、更高效,但目前这个方法可行。这里我们引用上面声明的全局 GBookmarks
对象。我们使用 foreach
循环遍历,并将条目添加到我们的 ListView
中。
void GBookmarks_BookmarksRefreshed(object sender, BookmarksRefreshedEventArgs e)
{
listView1.Items.Clear();
foreach (GBDemoClass.BookmarkProperties bookmark in GBookmarks)
{
ListViewItem iBookmark = new ListViewItem();
iBookmark.Text = bookmark.Title;
iBookmark.SubItems.Add(bookmark.URL);
iBookmark.SubItems.Add(bookmark.Category);
iBookmark.Subitems.Add(bookmark.Id);
listView1.Items.Add(iBookmark);
}
}
现在我们只需要初始化该类,调用 Refresh
,然后坐等一切发生。
GBookmarks.Init("UserName", "Password");
GBookmarks.Refresh();
当书签检索完毕后,会触发 BookmarksRefreshed
事件,您的列表也将更新。这可以轻松地应用于工具栏或其他类似的控件来构建菜单。
添加 / 编辑 / 删除
GBookmarks.Add(string Category, string Title, string URL);
GBookmarks.Remove(string ID);
Add
函数同时处理添加和编辑。关键似乎在于 URL。添加任何具有已存在于其他书签中的 URL 的类别/标题的组合都会修改该书签,而不是添加重复项。这是 Google 设计此服务的方式,并非我代码所为。我有一个单独的删除函数,但方法是相同的,“如果您调用带空类别和标题但填充了 URL 的 Add
,您将删除该条目”。我发誓昨天是这样的,但在重写这篇文章时,我发现上一句话不准确了。今天我发现要删除一个条目需要不同的 POST 变量集,并且现在需要 ID
。
此外,Google 在服务器端有完整性检查,您无法添加无效的 URL。
工作原理
对于那些想了解幕后运作方式的人,请继续阅读。
初始化类
初始化期间,将 username
和 password
传递给 Init
函数。请注意,如果存在登录 cookie,使用备用凭据多次调用 Init
将无法产生所需的结果。我将 Login
方法设为 public
,以便该对象可以重用于多个帐户。已删除前一版本中使用的 ArrayLists
。此外,Init
结束时不再调用 Refresh
。
public void Init(string username, string password)
{
if (string.IsNullOrEmpty(username))
{
throw new ArgumentNullException("username");
}
if (string.IsNullOrEmpty(password))
{
throw new ArgumentNullException("password");
}
_Username = username;
_Password = password;
}
在调用 Refresh
之前,您可以使用 UseTimer
属性(Boolean
)来确定是否使用自动刷新计时器。这与 UpdateInterval
属性(Int
)结合使用,后者以毫秒为单位设置更新时间。默认间隔为 10 分钟。请注意,UseTimer
和 UpdateInterval
可以在初始化之前或之后随时设置,但更改要到下次调用 Refresh
时才会生效。
让我们继续下一个函数,Refresh
。
public bool Refresh()
{
if (string.IsNullOrEmpty(_Username) || string.IsNullOrEmpty(_Password))
{
throw new InvalidOperationException("Username or Password not set");
}
_Bookmarks.Clear();
if (!UpdateBookmarks())
return false;
OnBookmarksRefreshed(_Username);
UpdateTimer();
return true;
}
在触发 BookmarksRefreshed
事件之前,上述代码首先调用 UpdateBookmarks
函数,然后处理计时器的任何更改,或者在出现错误时返回 false
。您会注意到此类倾向于分层。也就是说,一旦进程开始,调用堆栈会相当复杂,所以请尽量跟上。接下来我们将查看 UpdateBookmarks
。
private bool UpdateBookmarks()
{
CheckLogin();
DownloadBookmarksRSS();
return ParseRSS();
}
private void CheckLogin()
{
if (!ValidateCookies())
Login();
}
在此函数中,我们首先确定是否已登录。这是通过使用 cookie 和 CheckLogin
函数来完成的。跟踪 cookie 的过程经历了许多试验和不同的方法。最后,我们从正确的 URL 检查一个名为 SID
且值不等于 EXPIRED
的 cookie。我不会在此处粘贴函数(ValidateCookies
),如果您感兴趣,请检查源代码。
我们的下一步是执行登录。我们在 Login
中进行此操作。
private void Login()
{
string PostData = EncodePostData(
new string[] { "service", "nui", "hl", "Email", "Passwd",
"PersistentCookie", "rmShown", "continue" },
new string[] { "bookmarks", "1", "en", _Username, _Password, "yes", "1",
"http://www.google.com/bookmarks/lookup%3Foutput%3Dxml%26num%3D10000&" }
);
POST(_BookmarksLoginURL, PostData);
}
此函数将 POST 数据进行 URL 编码并调用 POST
,后者关联 Cookie 容器、设置各种标头并提交 POST
数据。成功调用此函数后,ValidateCookies
将返回 true
,表示我们已登录。如果您想了解 POST
或 EncodePostData
函数的工作原理,同样,请检查代码。
现在是时候实际检索书签了。这在 DownloadBookmarksRSS
函数中完成。让我们来看一下,好吗?
private void DownloadBookmarksRSS()
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_BookmarksRssUri);
request.CookieContainer = _CookieContainer;
using (WebResponse response = request.GetResponse())
using (StreamReader reader =
new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
_Data = reader.ReadToEnd();
}
}
catch
{
_Data = string.Empty;
}
}
如果您想知道 _BookmarksRssUri
和 _Data
的来源,它们是全局定义的。
很简单,对吧?幸运的是,Google 现在在 RSS feed 中包含 GUID
和 ID
标签。以前,需要获取并解析 RSS feed 和 XML feed 来获取所有必需的信息。如果在 DownloadBookmarksRSS
函数中发生任何错误,我们将 _Data
设置为空 string
,导致 ParseRSS
在下面返回 false
。如果一切正常,我们现在调用 ParseRSS
并添加填充我们的书签。
private bool ParseRSS()
{
if (!string.IsNullOrEmpty(_Data))
{
string title = string.Empty;
string url = string.Empty;
string category = string.Empty;
string guid = string.Empty;
string id = string.Empty;
using (XmlReader reader = XmlReader.Create(new StringReader(_Data)))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "item":
//New Item
if (title != string.Empty)
if (id != string.Empty)
_Bookmarks.Add(new BookmarkProperties(id, guid, title, category, url));
title = string.Empty;
url = string.Empty;
category = string.Empty;
guid = string.Empty;
id = string.Empty;
break;
case "title":
title = reader.ReadString();
break;
case "link":
url = reader.ReadString();
break;
case "smh:signature":
_Signature = reader.ReadString();
break;
case "guid":
guid = reader.ReadString();
break;
case "smh:bkmk_id":
id = reader.ReadString();
break;
case "smh:bkmk_label":
category = reader.ReadString();
break;
}
}
}
if (title != string.Empty)
if (id != string.Empty)
_Bookmarks.Add(new BookmarkProperties(id, guid, title, category, url));
}
return true;
}
return false;
}
我们必须检查 RSS feed 中是否存在 ID
,因为 Google 总是会有一个指向 http://google.com/bookmarks 的条目。这个链接将没有 ID
。
RSS 数据以 XML 形式接收,所以这里我们使用 XmlReader
来遍历所有节点。在上一版本中,**已知**每个条目都以 item
标签开头,并以 smh:bkmk_label
标签结尾。在添加添加和修改书签的功能时,我发现如果书签不在类别中,则 smh:bkmk_label
标签不存在。以前,我们会将标签找到视为条目已读取。这有效地隐藏了所有缺少类别的条目。我很惊讶到现在还没有其他人发现这个问题。为了解决这个问题,现在它会在每次读取新项时执行检查。如果标题不为空,则添加我们已有的内容。同样,我们必须在所有项读取完毕后执行相同的检查,否则我们会错过最后一个。捕获所有字段后,我们调用 _Bookmarks.Add
将其添加到列表中。最终,我将清理此函数,删除 case
语句,并直接从 XML feed 而不是 RSS feed 读取字段。
在所有这些函数执行完毕后,将触发 BookmarksRefreshed
事件,您可以根据可用数据进行操作。提供给事件处理程序的唯一值是 Username
,以防同时使用不同帐户的多个对象。
添加和编辑
如前所述,Add
方法用于添加和更新条目。每个书签似乎都必须有一个唯一的 URL。此外,URL 区分大小写,因此您可能有一个条目指向 google.com,也可能有一个条目指向 Google.com。尝试添加一个 URL 已存在的条目将导致其类别和标题更改为正在添加的内容。
public string Add(string Category, string Name, string URL)
{
CheckLogin();
string PostString = EncodePostData(
new string[] { "q", "title", "labels", "zx" },
new string[]
{ URL, Name, Category, Convert.ToString(DateTime.Now.Second * 3175) }
);
WebResponse response = POST("http://www.google.com/bookmarks/mark", PostString);
string respData;
if (response == null)
respData = "";
else
using (StreamReader reader =
new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
respData = reader.ReadToEnd();
}
return respData;
}
您会注意到在添加和编辑条目时出现的 zx
变量。此变量是一种时间戳,但我尚未确定其计算方式。我知道它一天会循环多次。因此,我们将其设置为当前秒数 * 3175。感谢 **Marschills** 提供了一些关于此的信息。
以前(不到 24 小时前!),我发现用空的类别和标题添加已存在的 URL 会导致其被删除;现在似乎不再是这样了,如果现在这样做只会导致类别被清除。通过进一步的嗅探,我确定删除条目与添加条目相同,只是 POST
变量不同。要删除一个条目,我们必须将 dlq
设置为该条目的 ID
,将 op
设置为 remove
,并将 zx
设置为我们的随机值。
感谢 Steven Hansen
好的,您上面看到了 _Bookmarks
对象。**Steve Hansen** 在下面的论坛帖子中将主类变成了可枚举的,并修改了 BookmarkProperties
类,将项目存储在 List
中并删除了 ArrayLists
的使用。
_Bookmarks
定义如下全局变量:
private List _Bookmarks = new List();
此外,通过声明 GoogleBookmarks
类为:
public class GoogleBookmarks : IEnumerable
请查看代码以获取我在这里没有提到的任何细节。
最后
这个类花了很多时间和精力以及大量的研究才完成。正如之前所述,我不知道任何其他控制、文档,甚至是在任何 .NET 语言中实现此任务的示例。我希望您觉得这篇文章有用,如果觉得有用,请投票!我也欢迎任何和所有反馈或评论。发现 bug 了吗?知道如何改进它吗?在此处发布,我将很可能将其纳入下一个版本。尽情享受吧!
当前限制
- Google 允许为单个书签分配多个类别(标签)。目前,该类不支持单个项目有多个类别,将使用最后读取的类别。这将在下一个版本中处理。
历史
- 2007 年 9 月 4 日 – 初次发布
- 2007 年 9 月 5 日 - 纠正了帖子模板对文章做的所有不当之处!
- 2008 年 3 月 23 日 – 第二版。实现了
Add
/Edit
/Remove