Commit Diff


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 @@
+<img width=150 src="tweets.png" align="right" />
+
+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 <https://apps.twitter.com/>.
+2. Get your consumer and access tokens from
+   <https://apps.twitter.com/app/[APP_ID]/keys>.
+   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)