Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweet new threads in NG.announce #97

Closed
wilzbach opened this issue Feb 1, 2018 · 13 comments
Closed

Tweet new threads in NG.announce #97

wilzbach opened this issue Feb 1, 2018 · 13 comments

Comments

@wilzbach
Copy link
Contributor

wilzbach commented Feb 1, 2018

From: https://forum.dlang.org/post/p4uubg$1rbl$1@digitalmars.com

<title> #dlang

I went ahead and created an account: https://twitter.com/dlang_ng and have send you the credentials.
I will look now at how to do this best in DFeed, but maybe it's a simple change for you?

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 1, 2018

Here's a TwitterSink - tweet is already working.
I didn't know how to get to the title, user and category of a post.
Plus I used std.net.curl as it was easier to debug.

module ircsink;

import std.datetime;
import std.string;

import common;
import user;

import ae.net.oauth.common;

final class TwitterSink : NewsSink
{
    static struct Config
    {
        string consumerKey;
        string consumerKeySecret;
        string oauthAccessToken;
        string oauthAccessTokenSecret;
    }

    this(Config config)
    {
        this.config = config;
        OAuthConfig oAuthConfig = {
            consumerKey: config.consumerKey,
            consumerSecret: config.consumerKeySecret
        };
        OAuthSession session = {
            config: oAuthConfig,
            token: config.oauthAccessToken,
            tokenSecret: config.oauthAccessTokenSecret
        };
        this.session = session;
    }

protected:
    override void handlePost(Post post, Fresh fresh)
    {
        import std.uri;

        if (!fresh)
            return;

        if (post.time < Clock.currTime() - dur!"days"(1))
            return; // ignore posts older than a day old (e.g. StackOverflow question activity bumps the questions)

        // TODO: if NG.announce
        // TODO: get title + user
        tweet("%s by %s #dlang".format("title", "author"));
    }

    void tweet(string message)
    {
        import std.algorithm, std.array;
        import std.net.curl;
        import std.format;
        import std.datetime;
        import std.stdio;

        import ae.net.http.common;
        import ae.net.ietf.url;

        auto parameters = new UrlParameters[](1);
        parameters[0]["status"] = message;
        string url = "https://api.twitter.com/1.1/statuses/update.json";
        string queryString = parameters.map!(ps => ps.pairs.map!(p => session.encode(p.key) ~ "=" ~ session.encode(p.value))).joiner.join("&");
        auto http = HTTP((url ~ "?" ~ queryString));
        http.method = HTTP.Method.post;
        http.addRequestHeader("Authorization", session.prepareRequest(url, "POST", parameters).oauthHeader);
        http.verbose = true;
        http.contentLength = 0;
        http.perform();
    }

    private:
    immutable Config config;
    OAuthSession session;
}

(I won't have time to work on this for the next days - if you have time and want to finish this, please do)

@CyberShadow
Copy link
Owner

Thanks for this! I'll add this in a bit.

import ae.net.oauth.common;

Wow, I totally forgot I wrote that.

auto parameters = new UrlParameters[](1);

Any reason this has to be an array that I'm not seeing?

string queryString = parameters.map!(ps => ps.pairs.map!(p => session.encode(p.key) ~ "=" ~ session.encode(p.value))).joiner.join("&");

Does Twitter really expect the GET parameters to be encoded exactly the same as OAuth ones? I.e. wouldn't a normal encodeUrlParameters suffice here?

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 2, 2018

You are very welcome. As you know I have realized that things happen a lot faster when you do most of the work :)

Any reason this has to be an array that I'm not seeing?

That's what your oAuth API expects.

Does Twitter really expect the GET parameters to be encoded exactly the same as OAuth ones? I.e. wouldn't a normal encodeUrlParameters suffice here?

Yeah, it took a lot of trial and error to figure out how to do it correctly. Note that encode is a specific version of this:

static alias oauthEncode = encodeUrlPart!(c => std.ascii.isAlphaNum(c) || c=='-' || c=='.' || c=='_' || c=='~');

@CyberShadow
Copy link
Owner

That's what your oAuth API expects.

The method is variadic, so it can either take an array or any number of arguments of that type.

@CyberShadow
Copy link
Owner

Hmm, I can't get it to work... just get 401 Unauthorized.

Are you sure the OAuth session strings are meant to be part of the configuration, and we aren't supposed to get them from some authentication endpoint?

Pushed a WIP: https://github.com/CyberShadow/DFeed/blob/master/src/dfeed/sinks/twitter.d

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 2, 2018

Yeah, here's how I tested it without running DFeed:

void main(string[] args)
{

import std.algorithm, std.array;
import std.net.curl;
import std.format;
import std.datetime;
import std.stdio;

import ae.net.oauth.common;
import ae.net.http.common;
import ae.net.ietf.url;

static struct Config
{
	string consumerKey = "xxx";
	string consumerKeySecret = "xxx";
	string oauthAccessToken = "xxx";
	string oauthAccessTokenSecret = "xxx";
}
Config config;
OAuthConfig oAuthConfig = {
	consumerKey: config.consumerKey,
	consumerSecret: config.consumerKeySecret
};
OAuthSession session = {
	config: oAuthConfig, token:
	config.oauthAccessToken,
	tokenSecret: config.oauthAccessTokenSecret
};
auto parameters = new UrlParameters[](1);
parameters[0]["status"] = "%s by %s #dlang".format("title", "author");
string url = "https://api.twitter.com/1.1/statuses/update.json";
string queryString = parameters.map!(ps => ps.pairs.map!(p => session.encode(p.key) ~ "=" ~ session.encode(p.value))).joiner.join("&");
auto http = HTTP((url ~ "?" ~ queryString));
http.method = HTTP.Method.post;
http.addRequestHeader("Authorization", session.prepareRequest(url, "POST", parameters).oauthHeader);
http.verbose = true;
http.contentLength = 0;
http.perform();
}

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 2, 2018

Are you sure the OAuth session strings are meant to be part of the configuration, and we aren't supposed to get them from some authentication endpoint?

That's only if we would do OAuth for costumer - for sole API usage Twitter provides these "consumer keys".

@CyberShadow
Copy link
Owner

OK, figured it out.

@CyberShadow
Copy link
Owner

Deployed; hopefully it will work.

BTW, a much simpler way to do this would have been to plug in https://forum.dlang.org/feed/threads/announce into https://ifttt.com/connect/feed/twitter OSLT.

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 2, 2018

BTW, a much simpler way to do this would have been to plug in https://forum.dlang.org/feed/threads/announce into https://ifttt.com/connect/feed/twitter OSLT.

I made bad experiences with such services in the past. They tend to randomly break, limit the usage, or just start to charge you for it.
For example, I setup Zapier for copying the ical calendars from the Meetup events to a global D calendar, but after a few months they reduced their "free" API usage to five Meetups.
I thought you were the one usually pushing for things to be under our own control or open source.

Deployed; hopefully it will work.

Awesome! Thanks!

@CyberShadow
Copy link
Owner

I made bad experiences with such services in the past. They tend to randomly break, limit the usage, or just start to charge you for it.
For example, I setup Zapier for copying the ical calendars from the Meetup events to a global D calendar, but after a few months they reduced their "free" API usage to five Meetups.

Ouch.

I thought you were the one usually pushing for things to be under our own control or open source.

Yes, but Twitter already isn't, so how much worse can it get? :)
(Especially for a low-importance task like here)

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 2, 2018

Works nicely: https://twitter.com/dlang_ng/status/959511178656575489 :)

Do you want to make a short announcement?

@CyberShadow
Copy link
Owner

CyberShadow commented Feb 2, 2018 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants