commit 0c159575fef3ab03ef66215753887d4044a22f3c from: Sergey Bronnikov date: Tue Aug 21 11:06:36 2018 UTC added twisource client commit - cb06e3c7bfba13d9d84df89000c89541a787cc1a commit + 0c159575fef3ab03ef66215753887d4044a22f3c blob - /dev/null blob + dcd85bd6b12bdb29d546551e39f421523d38466d (mode 644) --- /dev/null +++ twisource/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sergey Bronnikov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. blob - /dev/null blob + 201ef5c4b2b8d40eb1684ab7a6225f3f10a002fc (mode 644) --- /dev/null +++ twisource/README.md @@ -0,0 +1,57 @@ + + +Twisource +========= + +- is a Twitter client. It allows you to update status on Twitter from source +file. + +## Installation + +- Install Python requirements + +``` +$ pip install -r requirements.txt +``` + +- Add tweets to tweets.yml. +- Create file(s) with account(s) credentials. +- Validate source file with scheduled tweets: + +``` +$ ./twisource +``` + +- Commit updated file and publish tweets: + +``` +$ git commit -a +$ git push +$ ./twisource --publish +``` + +## Getting access tokens + +Has to be done once. + +1. Register your application via . +2. Get your consumer and access tokens from + . + Here you may have to confirm your phone number, only then Twitter will allow + you to update statuses within an application. +3. Save `consumer_key`, `consumer_secret`, `access_token`, + `access_token_secret`. + +## Users + +- [OpenVZ, CRIU, P.Haul](https://www.openvz.org) +- ... + +## Similar tools + +- [publishr](https://github.com/vti/publishr) +- [twty](https://github.com/mattn/twty) + +## Contacts + +[sergeyb@](https://twitter.com/estet) blob - /dev/null blob + 8752d21ecf190e04a1fea92c86924f76883d093b (mode 644) --- /dev/null +++ twisource/main.go @@ -0,0 +1,156 @@ +// Twitter https://github.com/kurrik/twittergo +// Twitter https://github.com/ChimeraCoder/anaconda +// Facebook https://github.com/huandu/facebook +// Tumblr https://github.com/mattcunningham/gumblr +// Instagram https://github.com/yanatan16/golang-instagram +// Reddit https://github.com/jzelinskie/geddit +// G+ https://godoc.org/google.golang.org/api/plus/v1 +// IRC https://github.com/sorcix/irc + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "github.com/kurrik/oauth1a" + "github.com/kurrik/twittergo" + "gopkg.in/yaml.v2" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" +) + +type secret struct { + AccessSecret string + AccessToken string + ClientSecret string + ClientToken string +} + +type Tweet struct { + Account string + Text string + Date string +} + +const ( + TZ = "Europe/Moscow" + FILE = "tweets.yml" + interval = 5 + timeFormat = "2006-01-02 15:04" +) + +var ( + client *twittergo.Client + req *http.Request + resp *twittergo.APIResponse + tweet *twittergo.Tweet +) + +func LoadCredentials(account string) (c *twittergo.Client, err error) { + var file = "settings-" + account + ".json" + creds, err := ioutil.ReadFile(file) + if err != nil { + log.Printf("%v", err) + return nil, err + } + var s secret + err = json.Unmarshal(creds, &s) + + config := &oauth1a.ClientConfig{ + ConsumerKey: s.ClientToken, + ConsumerSecret: s.ClientSecret, + } + + log.Printf("DEBUG: AccessSecret %v\n AccessToken %v\n", s.AccessSecret, s.AccessToken) + log.Printf("DEBUG: ClientSecret %v\n ClientToken %v\n", s.ClientSecret, s.ClientToken) + user := oauth1a.NewAuthorizedConfig(s.AccessToken, s.AccessSecret) + client := twittergo.NewClient(config, user) + return client, nil +} + +func main() { + + flag.Usage = func() { + fmt.Printf("twisource is a Twitter client with collaboration support.\n\n") + fmt.Printf("Usage: twisource [options]\n\n") + flag.PrintDefaults() + } + + var mode = *flag.Bool("publish", false, "mode to publish tweets") + flag.Parse() + + if mode { + log.Printf("Mode is public.") + } + + filename, _ := filepath.Abs(FILE) + yamlFile, err := ioutil.ReadFile(filename) + + if err != nil { + panic(err) + } + + tweets := []Tweet{} + + err = yaml.Unmarshal(yamlFile, &tweets) + if err != nil { + log.Fatalf("error: %v", err) + } + + now := time.Now() + for _, t := range tweets { + log.Printf(" Text %s\n", t.Text) + log.Printf(" Account %s\n", t.Account) + log.Printf(" Date %s\n", t.Date) + sched, err := time.Parse(timeFormat, t.Date) + if err != nil { + log.Fatalf("error: %v", err) + } + if int64(sched.Sub(now).Minutes()) > 5 { + log.Println("[DEBUG] Less than 5 min") + client, err := LoadCredentials(t.Account) + if err != nil { + log.Printf("[DEBUG] Failed to load credentials: %v\n", err) + } + + data := url.Values{} + data.Set("status", fmt.Sprintf("Hello %v!", time.Now())) + body := strings.NewReader(data.Encode()) + req, err = http.NewRequest("POST", "/1.1/statuses/update.json", body) + if err != nil { + fmt.Printf("Could not parse request: %v\n", err) + os.Exit(1) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err = client.SendRequest(req) + if err != nil { + fmt.Printf("Could not send request: %v\n", err) + os.Exit(1) + } + + tweet = &twittergo.Tweet{} + err = resp.Parse(t.Text) + if err != nil { + if rle, ok := err.(twittergo.RateLimitError); ok { + log.Printf("Rate limited, reset at %v\n", rle.Reset) + } else if errs, ok := err.(twittergo.Errors); ok { + for i, val := range errs.Errors() { + fmt.Printf("Error #%v - ", i+1) + fmt.Printf("Code: %v ", val.Code()) + fmt.Printf("Msg: %v\n", val.Message()) + } + } else { + fmt.Printf("Problem parsing response: %v\n", err) + } + os.Exit(1) + } + } + } +} blob - /dev/null blob + c52eb68082f2e0c9a268925967832be2e4c526f1 (mode 644) --- /dev/null +++ twisource/requirements.txt @@ -0,0 +1,4 @@ +tweepy==2.1 +PyYAML==3.11 +argparse +pytz blob - /dev/null blob + af8ac2d3bcf5188efdd67e3862bd10f884ba8812 (mode 755) --- /dev/null +++ twisource/settings-estet.json @@ -0,0 +1,6 @@ +{ + "AccessSecret": "nTtc6nKzKSNtxuNdPgtB7SubM2dddlkalcmlLJlOE8Ik0C4Sz", + "AccessToken": "11690312-ZJ1sVHHHTBdeEol7rNO7tKkxsWBgEIwtla2bulwk", + "ClientSecret": "sOwsx28hFZS59esb3TgR4vZfDAfsJ1V1FDFmysHkcgKfrcUDuz", + "ClientToken": "wRjW0uVyYVoHpyJ1myBxbOaML" +} blob - /dev/null blob + c873986d3e79767dbc33fb28a3a638ef14a64b1f (mode 644) Binary files /dev/null and twisource/tweets.png differ blob - /dev/null blob + fcc913236c3002716a0700ea1c171586e68c6507 (mode 644) --- /dev/null +++ twisource/tweets.yml @@ -0,0 +1,5 @@ +--- + +- account: estet + text: "Twisource is a collaboration Twitter client to post tweets from file https://goo.gl/O7bCuf" + date: 2015-10-22 20:34 blob - /dev/null blob + d0527ae651adcf5f16a90432a6da5e07432f33c8 (mode 755) --- /dev/null +++ twisource/twisource.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +# BSD license, Sergey Bronnikov + +import argparse +import os +import re +import tweepy +from pytz import timezone +import datetime +import yaml +try: + import json +except ImportError: + import simplejson as json + +tz = 'Europe/Moscow' +tweet_file = 'tweets.yml' + + +def lint(message, date, account): + if len(message) > 140: + print "[DEBUG] The text of your tweet scheduled at", date, "is too long" + return 1 + return 0 + + f = "settings-" + account + ".json" + if not (os.path.isfile(f) and os.access(f, os.R_OK)): + print "[DEBUG] File with credentials is not accessible" + return 1 + return 0 + + +def getRTid(message): + m = re.match("^RT ([0-9]*)$", message) + if m: + return m.groups()[0] + + +def cred(account): + f = "settings-" + account + ".json" + with open(f) as fp: + return json.load(fp) + + +def tweeter(message, account, mode): + credentials = cred(account) + auth = tweepy.OAuthHandler(credentials['ClientToken'], credentials['ClientSecret']) + auth.set_access_token(credentials['AccessToken'], credentials['AccessSecret']) + api = tweepy.API(auth) + rt_id = getRTid(message) + if rt_id: + print "[DEBUG] %s: RT https://twitter.com/statuses/%s" % (account, rt_id) + else: + print "[DEBUG] %s: %s" % (account, message) + if not rt_id: + if mode: + print "[DEBUG] Posting a message", message + api.update_status(message) + print "[DEBUG] Posted" + else: + if mode: + print "[DEBUG] Retweeting a message", rt_id + api.retweet(rt_id) + print "[DEBUG] Posted" + + +def main(mode): + with open(tweet_file, 'r') as f: + tweets = yaml.load(f) + + d = datetime.datetime.now(timezone(tz)) + print "[DEBUG] Time in timezone %s - %s" % (tz, d.strftime("%Y-%m-%d %H:%M")) + for t in tweets: + if t['date'] >= d.strftime("%Y-%m-%d %H:%M") and not lint(t['text'], t['date'], t['account']) and not mode: + tweeter(t['text'], t['account'], mode) + if t['date'] == d.strftime("%Y-%m-%d %H:%M") and not lint(t['text'], t['date'], t['account']) and mode: + tweeter(t['text'], t['account'], mode) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Post Twitter messages from file.') + parser.add_argument("-p", "--publish", help="post scheduled messages", action='store_true') + args = parser.parse_args() + + if args.publish: + print "[DEBUG] publish mode enabled" + else: + print "[DEBUG] lint mode enabled" + + main(mode=args.publish)