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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.32/5 (7投票s)

2012年2月6日

CPOL

6分钟阅读

viewsIcon

31025

downloadIcon

580

在分布式应用程序工作流中使用 Twitter 作为全局可用的持久消息队列

324332/Drive_your_application_through_Twitter_direct_messages.jpg

引言

我相信这个概念肯定已经被其他人使用了,但以我有限的搜索能力并没有发现。简单来说,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:首次发布。等待反馈。
© . All rights reserved.