通过 Twitter 私信驱动您的应用程序






4.32/5 (7投票s)
在分布式应用程序工作流中使用 Twitter 作为全局可用的持久消息队列
引言
我相信这个概念肯定已经被其他人使用了,但以我有限的搜索能力并没有发现。简单来说,Twitter 就像一个巨大的持久化消息队列,并提供丰富的 API 来查询它。理论上,因此可以从任何 Twitter 客户端将特定类型的消息入队到队列中,然后由某种能够理解其含义的监听器将其出队。将消息定向到特定监听器的挑战很容易解决。我为我的工作场所构建了一个系统(只是一个概念验证),人们可以通过特定格式发送私信给这个监听器,然后这些私信会被翻译成我们内部系统中的工时单。他们当然可以使用任何 Twitter 客户端来完成。理论上,这是一个免费的(!)公共消息队列,内置了一定程度的隐私(如果您使用私信而不是推文)。我认为,如果少量使用,这不会被视为滥用 Twitter 服务。这是一份免责声明 - 请自行判断使用此概念。附带的项目使用可爱的 TweetSharp API 与 Twitter API 交互,通过 oAuth 进行身份验证。有一个基本的插件机制使用 MEF 来处理“处理器”,这些处理器处理私信以完成有意义的工作。
它受我们想象力的限制,因为我们可以如何利用这种机会。一位财务总监可以通过发送一条简短的私信,如“发送报告 2011-12”,来请求最新的财务报告到他的邮箱,从而确保信息的安全。一位授权者可以使用私信批准一笔费用报销。
背景
理论上,Twitter 可以被认为是一个庞大且持久化的消息队列。每一条推文就像一个有意义的数据包(或者不是?)。如果消息只有对您的系统才有意义怎么办?如果消息包含驱动您系统中工作流的信息怎么办?如果您的系统处理这些消息并完成有意义的工作怎么办?那么,您是否就能从世界任何地方、任何客户端、任何设备向您的系统发送消息了?是的?而且您无需在工作域之外的任何服务器上部署任何东西!当然,理论上,Facebook 也可以取代 Twitter 来实现相同的效果。甚至电子邮件系统也可以用一种笨拙的方式来完成同样的事情。
当然,这里的数据安全性并不高。我们使用 Twitter 内置的私信服务来确保相对的隐私。我们可以通过使消息简短且隐晦来最大化数据安全,但这会牺牲可用性。用户希望输入简短、有意义且易于记住的短语。本文不探讨如何解决数据安全问题。
使用代码
代码非常容易阅读。请随时提问。
该解决方案使用了来自非常友善的人们在公共领域提供的代码片段。该应用程序有一个 WPF 前端,它使用 oAuth 连接到您的 Twitter 帐户,并持久化密钥以供将来调用 Twitter API。然后,它会轮询您的帐户以获取特定签名(格式)的私信。签名定义在您编写的扩展 DLL 中。我添加了一个示例扩展 DLL,它监听类似“pop hello Isha”的消息,然后显示一个 Windows 消息对话框,上面写着有人说了“Isha”。这里“pop”是处理器的名称,“hello”是操作。其余的关键字是参数。请查看此类的源代码。
在我的设置中,我有一个 DLL,它知道如何在我们的内部系统中创建工时单记录。它期望的私信格式如下:- ts create p="Product 02 Release 9" a="Project Team Leading" d=06/11/2011 h=7。
MEF 已被用作 IOC 容器,用于插入消息处理器和发送通知的服务。
关注点
启动嵌入式 WebBrowser 以运行 oAuth 工作流的过程非常有趣。
private void OnUrlLoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
if (string.Compare(e.Uri.AbsoluteUri, "https://api.twitter.com/oauth/authorize", true) == 0)
{
if (!e.Uri.Query.Contains("oauth_token"))
{
var doc = this._authWebBrowser.Document as mshtml.HTMLDocument;
// Get the user name here, so that you can persist the keys by user name
var user = doc.getElementById("session") as mshtml.HTMLDivElement;
if (user != null)
{
string userText = user.innerText.Trim();
string twitterName = userText.Split(' ').FirstOrDefault();
if (twitterName.Length > 0)
{
AppUser = twitterName;
}
}
// Check if there is DIV with id="oauth_pin"
var oauthPinElement = doc.getElementById("oauth_pin") as mshtml.IHTMLElement;
if (null != oauthPinElement)
{
var div = oauthPinElement as mshtml.HTMLDivElement;
if (null != div)
{
var pinText = div.innerText;
if (!string.IsNullOrEmpty(pinText))
{
// We have validation
OAuthPin = pinText.Trim();
MatchCollection collection = Regex.Matches(OAuthPin, @"\d+");
if (collection.Count > 0)
{
OAuthPin = collection[0].Value;
}
Authorized = true;
}
}
}
else
{
// User has deined access.
Authorized = false;
}
this.DialogResult = true;
this.Close();
}
}
}
MEF 框架需要处理器和服务 DLL。处理器扩展 DLL 的名称必须是 `*.TweetProcessor.dll` 的形式,并且必须位于可执行文件相对路径下的 `\extension` 文件夹中。同样,服务扩展 DLL 的名称必须是 `*.Service.dll` 的形式,并且必须位于可执行文件相对路径下的 `\extension` 文件夹中。
[InheritedExport(typeof(IMessagingService))]
public interface IMessagingService
{
bool SendEMail(string from, string[] toList, string subject,
string body, out string error);
}
[InheritedExport(typeof(IProcessingElement))]
public interface IProcessingElement
{
[Description("Command name")]
string Moniker { get; }
//List<CommandOptions> CommandOptionsCollection { get; }
IEnumerable<imessagingservice /> MessagingServices { get; set; }
}
public class TweetProcess
{
[ImportMany(typeof(IProcessingElement))]
IEnumerable<IProcessingElement> ListProcessors { get; set; }
[ImportMany(typeof(IMessagingService))]
IEnumerable<imessagingservice /> MessagingServices { get; set; }
public TweetProcess()
{
string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
ListProcessors = new CompositionContainer(
new DirectoryCatalog(path + @"\Extensions",
"*.TweetProcessor.dll")).GetExportedValues<iprocessingelement />();
MessagingServices = new CompositionContainer(
new DirectoryCatalog(path + @"\Extensions",
"*.Service.dll")).GetExportedValues<imessagingservice />();
foreach (IProcessingElement element in ListProcessors)
{
element.MessagingServices = MessagingServices;
}
}
TweetSharp 是一个很棒的库,用于访问 Twitter。我不确定它是否还在维护。TweetSharp 可以轻松地查询 Twitter。
public static bool Authenticate(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret)
{
return FluentTwitter.CreateRequest().AuthenticateWith(consumerKey, consumerSecret,
accessToken, accessTokenSecret).Statuses().OnUserTimeline().Request().ResponseHttpStatusCode != 401;
}
public void DeleteAllDirectMessages(string consumerKey, string consumerSecret,
string accessToken, string accessTokenSecret)
{
var directMessages = FluentTwitter.CreateRequest().AuthenticateWith(consumerKey, consumerSecret,
accessToken, accessTokenSecret).DirectMessages().Sent().Take(200).Request().AsDirectMessages();
foreach (TwitterDirectMessage directMessage in directMessages)
{
var x = FluentTwitter.CreateRequest().AuthenticateWith(consumerKey, consumerSecret,
accessToken, accessTokenSecret).DirectMessages().Destroy(directMessage.Id).Request();
}
}
最后,我每 10 秒轮询一次 Twitter 以获取任何新的私信。您可能希望使用 Twitter 流 API 来更有效地完成此操作,但我还没有探索过这个选项。
internal void Start(string consumerKey, string consumerSecret, string accessToken,
string accessTokenSecret, TweetProcessor.MainWindow.UpdateListDelegate dg)
{
while (true)
{
ProcessDiretMessages(consumerKey, consumerSecret, accessToken, accessTokenSecret, dg);
System.Threading.Thread.Sleep(10000);
}
}
`ProcessDirectMessages` 获取自上次读取以来的私信,解析消息,然后根据消息中的关键字调用相关的消息处理器。
var directMessagesRequest = FluentTwitter.CreateRequest().AuthenticateWith(consumerKey, consumerSecret,
accessToken, accessTokenSecret).DirectMessages().Received();//.Take(200).Request().AsDirectMessages();
IEnumerable<TwitterDirectMessage> directMessages = null; ;
if (uLastId > 0)
{
directMessages = directMessagesRequest.Since(uLastId).Request().AsDirectMessages();
}
else
{
directMessages = directMessagesRequest.Take(200).Request().AsDirectMessages();
}
bool success = true;
string lastTweetId = string.Empty;
string message = string.Empty;
if (directMessages != null)
{
foreach (TwitterDirectMessage directMessage in directMessages.OrderBy(s => s.Id))
{
success = ProcessTweet(directMessage, out message);
lastTweetId = directMessage.Id.ToString();
//Delete the direct message
FluentTwitter.CreateRequest().AuthenticateWith(consumerKey, consumerSecret, accessToken,
accessTokenSecret).DirectMessages().Destroy(directMessage.Id).Request();
//TwitterResult result = FluentTwitter.CreateRequest().AuthenticateWith(consumerKey,
// consumerSecret, accessToken, accessTokenSecret).DirectMessages().Send(directMessage.SenderScreenName,
// (message.Length > 110 ? message.Substring(0, 110) : message) + " " + DateTime.Now.ToString()).Request();
if (!success)
{
string temp = string.Empty;
TrySendMessage("rahul.kumar@sage.com", "rahul.kumar@sage.com",
directMessage.Sender.ScreenName,
directMessage.Text + Environment.NewLine + message, out temp);
}
List<string /> list = new List<string />();
list.Add(string.Format("{0}:{1} - {2}", directMessage.Sender.ScreenName,
directMessage.Text, message));
dg.DynamicInvoke(new object[] { list });
}
}
请查看 `CommandOptionsFactory` 类,其中包含解析私信以提取要执行的命令片段的代码。
public class CommandOptionsFactory
{
public static T ParseOptions(string narrative) where T : new()
{
if (typeof(T).BaseType != typeof(CommandOptions))
throw new System.ArgumentException("Only works with CommandOptions objects");
narrative = ProcessQuotes(narrative);
List<string> fragments =
narrative.Split(' ').Select(s => s.Trim()).Where(s => s.Length > 0).ToList();
T options = new T();
Type type = typeof(T);
PropertyInfo[] infos = type.GetProperties();
bool success = true;
foreach (string fragment in fragments)
{
success &= ProcessOption(fragment, options as CommandOptions, infos);
}
(options as CommandOptions).Initialised = success;
return options;
}
public const string SPACE = "_|_";
private static string ProcessQuotes(string narrative)
{
// Get the quotes
List<string> fragments = narrative.Split('"').ToList();
for (int i = 1; i < fragments.Count(); i += 2)
{
//Convert the spaces to a symbol - SPACE
fragments[i] = fragments[i].Split(' ').Select(
s => s.Trim()).Where(s => s.Length > 0).Aggregate((a, b) => a + SPACE + b);
}
return fragments.Aggregate((a, b) => a + b);
}
private static bool ProcessOption(string fragment, CommandOptions options, PropertyInfo[] infos)
{
bool success = false;
List<string> pair = fragment.Split('=').ToList();
if (pair.Count > 1)
{
foreach (PropertyInfo info in infos)
{
object[] attribs = info.GetCustomAttributes(typeof(ParamAttribute), false);
if (attribs != null && attribs.FirstOrDefault() != null)
{
ParamAttribute param = attribs.First() as ParamAttribute;
if (param != null)
{
if (string.Compare(pair.First(), param.Name, true) == 0)
{
info.SetValue(options, pair[1], new object[] { });
success = true;
break;
}
}
}
}
}
return success;
}
}
要运行该解决方案,您必须有两个 Twitter 帐户 - 一个用于服务器,另一个用于客户端。这两个帐户必须相互关注才能使 DM(私信)正常工作。当您运行应用程序时,请通过 oAuth 工作流授权应用程序读取/删除您选择作为服务器的 Twitter 帐户上的消息。现在使用第二个 Twitter 帐户向服务器 Twitter 帐户发送一条私信。发送类似 **pop hello Isha** 的消息。这将弹出一个窗口对话框,显示“hello Isha”。顺便说一下,Isha 是我可爱的 15 个月大的女儿。
此演示涵盖了将私信发送到服务器的过程。现在由您编写 MEF 扩展来执行有意义的工作。此外,您还需要定义驱动工作流的语法。
您必须在您的 Twitter 登录(开发模式)中创建一个应用程序,并注册此应用程序(TweetProcessor 或您想给的任何其他名称)。从 Twitter 上新创建的应用程序获取 ConsumerKey 和 ConsumerSecret,并将其粘贴到此演示解决方案的 `Settings.settings` 中。现在您应该可以使用了。如果您有任何问题,请告诉我。
历史
- 2012/01/30:首次发布。等待反馈。