tweep 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +3 -0
- data/MIT-LICENSE.txt +16 -0
- data/README.markdown +129 -0
- data/accounts/your_account_1.yml +31 -0
- data/bin/tweep +6 -0
- data/lib/tweep.rb +27 -0
- data/lib/tweep/account.rb +67 -0
- data/lib/tweep/config.rb +120 -0
- data/lib/tweep/core_exts.rb +48 -0
- data/lib/tweep/index.rb +42 -0
- data/lib/tweep/logging.rb +33 -0
- metadata +85 -0
data/CHANGELOG.markdown
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Copyright (c) 2011 Christophe Porteneuve
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
11
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
12
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
13
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
14
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
15
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
16
|
+
SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
Tweep - Automatic Twitter Peeping. Lets you rotate through tweets in a scheduled manner, with multiple accounts, and auto-retweet such tweets on other accounts yet. For instance, think recent accounts for product launches versus their more established company accounts.
|
2
|
+
|
3
|
+
# Installing
|
4
|
+
|
5
|
+
Tweep is in Ruby, so you need Ruby and RubyGems installed. It’s probably already there on your OSX or Linux box, and on Windows you can use the nifty [Ruby Installer](http://rubyinstaller.org/downloads/). Then just install the gem to get the `tweep` command:
|
6
|
+
|
7
|
+
```
|
8
|
+
$ gem install tweep
|
9
|
+
```
|
10
|
+
|
11
|
+
# Configuring your accounts
|
12
|
+
|
13
|
+
Accounts are detected by inspecting YAML files in the `accounts` subdirectory in the current working path. The base name of the file can be anything, as the actual account is identified by the authentication information inside.
|
14
|
+
|
15
|
+
To tweet on behalf of an account, Tweep needs this account’s **consumer key and secret**, along with **OAuth token and secret** for an app you setup on that account with Read/Write access (no DM access is necessary).
|
16
|
+
|
17
|
+
## Creating custom applications for your accounts for Tweep to use
|
18
|
+
|
19
|
+
Tweep is no central application that could prompt Twitter to authorize it, otherwise open-sourcing it with its OAuth tokens would be quite problematic: any user would be able to tweet as any other!
|
20
|
+
|
21
|
+
You will need to create a custom app on your account for Tweep to use on your behalf. Here’s how:
|
22
|
+
|
23
|
+
1. Sign in to [Twitter Developers](http://dev.twitter.com) using your regular Twitter account. This should get you to the "My Applications" screen.
|
24
|
+
2. Click the "Create an app" link
|
25
|
+
3. Give it a name (say, "Tweep"). This is the name that will show up as "the source" (the app you use to tweet) in your Tweep-sent tweets.
|
26
|
+
4. description (anything) and website (your own perhaps).
|
27
|
+
5. If you're brave and have some spare time, read the terms of use, cheekily renamed "Developer Rules of the Road." Then check the "Yes, I agree" box.
|
28
|
+
6. Type the CAPTCHA. If it's unreadable, use the circular arrow icon on the right-hand side until you can manage it.
|
29
|
+
7. Finally, click the "Create your Twitter application" button.
|
30
|
+
|
31
|
+
OK, almost there. Your app starts out as read-only: it won't let you *send tweets* to Twitter. You're now on the app's screen.
|
32
|
+
|
33
|
+
8. Click the Settings tab
|
34
|
+
9. In Application Type, choose Read and Write
|
35
|
+
10. At the bottom of the form, click "Update this Twitter application’s settings"
|
36
|
+
11. Get back to the Details tab, and at the bottom, click "Create my access token"
|
37
|
+
|
38
|
+
Yay, you’re done! Click the OAuth tool tab and you’ll see the 4 pieces of authentication configuration you’ll need to put in your account’s YAML file. Use the `your_account_1.yml` file as a template and copy-paste the 4 values in the proper places. Make sure you keep the values wrapped in the quotes originally placed in the YAML file.
|
39
|
+
|
40
|
+
## Why can't I just put in my login and password?
|
41
|
+
|
42
|
+
First, because that's quite unsafe. Your password is likely one you use in a number of other places; having it lie around in some script somewhere isn't the best idea.
|
43
|
+
|
44
|
+
Second, because login and password are primarily meant for direct human use. A growing number of APIs do not allow them for program-based authentication, and Twitter's API is certainly headed that way. So OAuth it is! I realize this requires a bit of extra work on your part when setting up your access, but security and privacy are worth it.
|
45
|
+
|
46
|
+
# Scheduling your tweets
|
47
|
+
|
48
|
+
Your tweets are just a series of items in the `tweets` part of your YAML file. Use one tweet per line, surrounded by double quotes (see examples in `your_account_1.yml`). If you do need a double-quote in there, escape it by putting a backslash (`\`) before it, as you’ll see in the demo YAML file.
|
49
|
+
|
50
|
+
Then your tweet schedule uses the `schedule` key. You can provide one sub-key per day of the week, using their English lowercase name (e.g. `tuesday`). Every such key lists hours of the day when to tweet. These hours use the time zone of the machine you’ll run Tweep on. You can use 24-hour or 12-hour format for times, and Tweep uses the hour and the (optional) minute part. Seconds are overkill, so we don't want them.
|
51
|
+
|
52
|
+
In 12-hour format, you can use 'am' or 'a', 'pm' or 'p' indifferently, using whatever case (upper or lower) you like best. Also, stick to US format: `12am` is midnight, `12pm` is noon, and there is no such thing as `0am` or `0pm`…
|
53
|
+
|
54
|
+
You can use multiple hours on the same day by separating hours with commas.
|
55
|
+
|
56
|
+
If you want to schedule tweets on specific dates besides (or instead of) recurring weekdays, you can use specific dates as schedule keys, in the form `YYYY-MM-DD`, for instance `2011-12-25` for December 25, 2011.
|
57
|
+
|
58
|
+
All this is nice and well, but the way you'll schedule the running of Tweep may end up launching it a few minutes late (perhaps because other, long tasks are run before it). So you can allow a maximum delay for running Tweep, using the `allowed_delay` key in `schedule`, expressed in minutes. For instance, setting `allowed_delay: 15` will let a task scheduled as `1p` be run until 01:15pm.
|
59
|
+
|
60
|
+
# Controlling automatic retweets
|
61
|
+
|
62
|
+
To define accounts that auto-retweet your tweets, you need two things:
|
63
|
+
|
64
|
+
1. Setup these accounts in their own files, with at minimum their authentication info so Tweep can make them retweet stuff.
|
65
|
+
2. List these accounts as retweeters in the "source" accounts’ YAML files.
|
66
|
+
|
67
|
+
The listing part is done through the `retweeters` key, which contains one subkey per retweeter. Subkeys are named **exactly** like the YAML files for these accounts (without the `.yml` extension).
|
68
|
+
|
69
|
+
## Retweeting every tweet
|
70
|
+
|
71
|
+
For an account to retweet *every single tweet* you write, you would use `always` as its definition. For instance, to make `mygroupie` retweet everything your `me` account tweets through Tweep, you would need the following in your `me.yml` file:
|
72
|
+
|
73
|
+
```yaml
|
74
|
+
retweeters:
|
75
|
+
mygroupie: always
|
76
|
+
```
|
77
|
+
|
78
|
+
This can be a bit extreme, so you may want to have such accounts retweet only every other tweet, or one tweet in three, or one in four… Just say so:
|
79
|
+
|
80
|
+
```yaml
|
81
|
+
retweeters:
|
82
|
+
mygroupie: every 3 tweets
|
83
|
+
mysupergroupe: every other tweet
|
84
|
+
```
|
85
|
+
|
86
|
+
We don't care about whether you actually say "tweet" or "tweets" at the end, and provide `other` as a nice-reading synonym for `2`. Should you say "every one," it'll be treated as a synonym to
|
87
|
+
"always".
|
88
|
+
|
89
|
+
# Executing your tweeting campaign
|
90
|
+
|
91
|
+
To run Tweep, just run the `tweep` command that is provided by the gem, in a directory that has the proper `accounts` subdirectory.
|
92
|
+
|
93
|
+
In order to keep track of where it stands in rotating your accounts’ tweets and observing the
|
94
|
+
"every so many tweets" policies, Tweep needs to be able to write to a `tweeping.idx` file in
|
95
|
+
the directory it’s running at.
|
96
|
+
|
97
|
+
You should then take steps to run the `tweep` command in the proper directory at regular intervals, frequently enough to honor your `schedule` settings. Linux and OSX have Crontab for this, and Windows has Scheduled Tasks.
|
98
|
+
|
99
|
+
## Logging
|
100
|
+
|
101
|
+
By default, the `tweep` executable logs its activity (tweets and retweets) to the standard output. You can redirect this manually to a file, forsake it to `/dev/null`, or ask it to log automatically to a `tweep.log` file in the current directory by using the `--log` command-line option.
|
102
|
+
|
103
|
+
# Contributing
|
104
|
+
|
105
|
+
Tweep intends to serve a rather simple need: doing basic Twitter campaigns and making sure
|
106
|
+
your tweets get added visibility from retweets by more prominent accounts of yours until
|
107
|
+
your originally-tweeting accounts get enough followers on their own. It's more of a
|
108
|
+
product- or service-launch thing, and for the relatively short term (a few months, perhaps
|
109
|
+
up to a year?).
|
110
|
+
|
111
|
+
If you have massively complex social media needs, you’re probably better off using an online
|
112
|
+
service such as [SocialOomph](https://www.socialoomph.com/) or
|
113
|
+
[CoTweet](http://cotweet.com/).
|
114
|
+
|
115
|
+
Still, if you discover a bug, or feel you can improve the user experience in a way
|
116
|
+
consistent with the initial design goals of Tweep, just fork the project on
|
117
|
+
[Github](https://github.com/tdd/tweep), patch it, test it, and submit a pull request.
|
118
|
+
I'll be happy to check these out and merge them in if I like them!
|
119
|
+
|
120
|
+
## Tests
|
121
|
+
|
122
|
+
I haven't written tests for this yet. I sure intend to. So stick around! And of course, contributing tests is most welcome. In order to reduce dependencies to a minimum, I will go with Test::Unit instead of fancier stuff like RSpec or Steak.
|
123
|
+
|
124
|
+
# License
|
125
|
+
|
126
|
+
Tweep is made available under the MIT license. Check the [MIT-LICENSE.txt](MIT-LICENSE.txt)
|
127
|
+
file in the soure code for details. TL;DR: as long as you keep the license in there with
|
128
|
+
due author and copyright info, you’re free to do whatever you want with Tweep, including
|
129
|
+
commercial uses. Just Don't Be Evil™.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Auth -- this is the only required part
|
2
|
+
consumer_key: ""
|
3
|
+
consumer_secret: ""
|
4
|
+
oauth_token: ""
|
5
|
+
oauth_token_secret: ""
|
6
|
+
|
7
|
+
# Schedule -- when to chomp through the tweets.txt file in the
|
8
|
+
# same directory, tweet the top of the list, rotate and update
|
9
|
+
# the file. Optional if this is just a retweeter account.
|
10
|
+
schedule:
|
11
|
+
monday: 1pm
|
12
|
+
thursday: 1pm
|
13
|
+
2011-12-25: 12am,12pm
|
14
|
+
|
15
|
+
tweets:
|
16
|
+
- "Blah blah blah o'blah"
|
17
|
+
- "Bleh bleh bleh bleh bleh"
|
18
|
+
- "Blih blah o'blah blouh\" bleh bleh"
|
19
|
+
|
20
|
+
# Retweets -- who retweets our tweets? You can either provide
|
21
|
+
# a single name as 'retweeter' or use the YAML array syntax inside
|
22
|
+
# a "retweeters" key. Tweep uses both if you do, "retweeter" being
|
23
|
+
# considered set to 'always'.
|
24
|
+
#
|
25
|
+
# Optional if this is just a retweeter account, that doesn't tweet on
|
26
|
+
# its own.
|
27
|
+
retweeter: porteneuve
|
28
|
+
|
29
|
+
retweeters:
|
30
|
+
porteneuve: always # Retweet every tweet.
|
31
|
+
rails: every 3 tweets
|
data/bin/tweep
ADDED
data/lib/tweep.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# Tweep - Automatic Twitter Peeping
|
5
|
+
# Lets you rotate through tweets in a scheduled manner,
|
6
|
+
# with multiple accounts, and auto-retweet such tweets on other accounts yet.
|
7
|
+
#
|
8
|
+
# (c) 2011 Christophe Porteneuve
|
9
|
+
|
10
|
+
require 'tweep/account'
|
11
|
+
require 'tweep/index'
|
12
|
+
require 'tweep/logging'
|
13
|
+
|
14
|
+
module Tweep
|
15
|
+
@@index = Index.new
|
16
|
+
|
17
|
+
def self.run!
|
18
|
+
info 'Running…'
|
19
|
+
Account.each &:run!
|
20
|
+
ensure
|
21
|
+
@@index.save!
|
22
|
+
end
|
23
|
+
|
24
|
+
Dir['accounts/*.yml'].each do |defn|
|
25
|
+
Account.new(defn, @@index)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# (c) 2011 Christophe Porteneuve
|
5
|
+
|
6
|
+
require 'tweep/config'
|
7
|
+
require 'rubygems'
|
8
|
+
require 'twitter'
|
9
|
+
|
10
|
+
module Tweep
|
11
|
+
class Account
|
12
|
+
@@registry = {}
|
13
|
+
|
14
|
+
def self.each(&block)
|
15
|
+
@@registry.values.each(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find(nick)
|
19
|
+
@@registry[nick]
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(yml, index)
|
23
|
+
return unless load_config(yml, index)
|
24
|
+
@index = index
|
25
|
+
@@registry[@config.nick] = self
|
26
|
+
end
|
27
|
+
|
28
|
+
def retweet!(status_id)
|
29
|
+
Tweep.info "#{@config.nick} retweets #{status_id.inspect}"
|
30
|
+
execute :retweet, status_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def run!
|
34
|
+
return unless @config.has_tweets? && @config.now_is_a_good_time?
|
35
|
+
tweet!
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def execute(call, *args)
|
40
|
+
Twitter.configure do |config|
|
41
|
+
@config.auth.each do |k, v|
|
42
|
+
config.send("#{k}=", v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
Twitter.send(call, *args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_config(file, index)
|
49
|
+
return unless File.file?(file) && File.readable_real?(file)
|
50
|
+
@config = Config.new(file, index)
|
51
|
+
@config.has_auth?
|
52
|
+
end
|
53
|
+
|
54
|
+
def tweet!
|
55
|
+
status, idx = @config.next_tweet
|
56
|
+
return if status.blank?
|
57
|
+
Tweep.info "#{@config.nick} tweets: #{status}"
|
58
|
+
st = execute(:update, status)
|
59
|
+
@index.tweeted! @config.nick, idx
|
60
|
+
@config.retweeters.each do |retweeter, _|
|
61
|
+
if @config.should_get_retweeted_by?(retweeter)
|
62
|
+
self.class.find(retweeter).try(:retweet!, st.id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/tweep/config.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# (c) 2011 Christophe Porteneuve
|
5
|
+
|
6
|
+
require 'tweep/core_exts'
|
7
|
+
|
8
|
+
module Tweep
|
9
|
+
class Config
|
10
|
+
attr_reader :auth, :nick, :schedule, :retweeters, :tweets
|
11
|
+
|
12
|
+
def initialize(file, index)
|
13
|
+
config = YAML::load(File.read(file))
|
14
|
+
|
15
|
+
@nick = File.basename(file, '.*')
|
16
|
+
@index = index
|
17
|
+
|
18
|
+
load_auth config
|
19
|
+
load_schedule config
|
20
|
+
load_retweeters config
|
21
|
+
|
22
|
+
@tweets = (config['tweets'] || []).map(&:strip)
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_auth?
|
26
|
+
4 == @auth.values.reject(&:blank?).size
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_tweets?
|
30
|
+
@tweets.any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def next_tweet
|
34
|
+
idx = @index.next_tweet_index(@nick)
|
35
|
+
idx = 0 if idx.to_i >= @tweets.size
|
36
|
+
[@tweets[idx], idx]
|
37
|
+
end
|
38
|
+
|
39
|
+
def now_is_a_good_time?
|
40
|
+
now = Time.now
|
41
|
+
(0..@allowed_delay.to_i).any? do |offset|
|
42
|
+
time = now - offset * 60
|
43
|
+
(@schedule[time.wday] || []).include?(time.strftime('%H:%M'))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def should_get_retweeted_by?(retweeter)
|
48
|
+
if (result = @index.retweet_timely?(@nick, retweeter))
|
49
|
+
@index.next_retweet_in! @nick, retweeter, @retweeters[retweeter]
|
50
|
+
else
|
51
|
+
@index.retweet_will_wait! @nick, retweeter
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
DOWS = %w(sunday monday tuesday wednesday thursday friday saturday)
|
58
|
+
|
59
|
+
TIME_REGEX = %r(
|
60
|
+
(?:
|
61
|
+
# 12-hour format - groups 1 and 2
|
62
|
+
((?:0?[1-9]|1[012])(?::[0-5][0-9])?)
|
63
|
+
([ap]m?)
|
64
|
+
|
|
65
|
+
# 24-hour format - group 3
|
66
|
+
((?:[01]?[0-9]|2[0-3])(?:[0-5][0-9]?))
|
67
|
+
)
|
68
|
+
)ix
|
69
|
+
|
70
|
+
def load_auth(config)
|
71
|
+
@auth = config.slice('consumer_key', 'consumer_secret', 'oauth_token', 'oauth_token_secret')
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_retweeters(config)
|
75
|
+
@retweeters = {}
|
76
|
+
if (shortcut = config['retweeter'])
|
77
|
+
@retweeters[shortcut.to_s] = 0
|
78
|
+
end
|
79
|
+
(config['retweeters'] || {}).each do |nick, pattern|
|
80
|
+
wait = 0
|
81
|
+
if pattern[/^\s*every\s+(\d+|other)(?:\s+tweets?)\s*$/i]
|
82
|
+
wait = 'other' == $1 ? 1 : [$1.to_i - 1, 0].max
|
83
|
+
end
|
84
|
+
@retweeters[nick] = wait
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def load_schedule(config)
|
89
|
+
@schedule = (config['schedule'] || {}).inject({}) do |acc, (dow, hours)|
|
90
|
+
key = DOWS.index(dow.to_s.downcase)
|
91
|
+
if !key && dow.to_s[/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/]
|
92
|
+
key = Date.civil($1.to_i, $2.to_i, $3.to_i) rescue nil
|
93
|
+
end
|
94
|
+
if !key && 'allowed_delay' == dow
|
95
|
+
@allowed_delay = hours.to_i
|
96
|
+
elsif key
|
97
|
+
hours = hours.split(',').map(&:strip).reject(&:empty?)
|
98
|
+
hours = hours.map { |hour| self.class.read_hour(hour) }.compact
|
99
|
+
acc[key] = hours if hours
|
100
|
+
end
|
101
|
+
acc
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.read_hour(hour)
|
107
|
+
return unless hour =~ TIME_REGEX
|
108
|
+
h, m = if $1 && $2
|
109
|
+
h, m = $1.split(':').map(&:to_i)
|
110
|
+
h = 0 if 12 == h
|
111
|
+
h += 12 if 'P' == $2.upcase[0, 1]
|
112
|
+
[h, m]
|
113
|
+
else
|
114
|
+
$3.split(':').map(&:to_i)
|
115
|
+
end
|
116
|
+
m ||= 0
|
117
|
+
"%02d:%02d" % [h, m]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# Tweep - Automatic Twitter Peeping
|
5
|
+
# Lets me rotate through tweets in a scheduled manner,
|
6
|
+
# with multiple accounts, and auto-retweet such tweets on other accounts yet.
|
7
|
+
#
|
8
|
+
# (c) 2011 Christophe Porteneuve
|
9
|
+
|
10
|
+
class Hash
|
11
|
+
# :nodoc:
|
12
|
+
# Inspired by ActiveSupport
|
13
|
+
def slice(*keys)
|
14
|
+
inject({}) do |acc, (k, v)|
|
15
|
+
acc[k] = v if keys.include?(k)
|
16
|
+
acc
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Object
|
22
|
+
alias_method :try, :__send__
|
23
|
+
end
|
24
|
+
|
25
|
+
class NilClass
|
26
|
+
# :nodoc:
|
27
|
+
# Inspired by ActiveSupport
|
28
|
+
def blank?; true; end
|
29
|
+
|
30
|
+
def try(*args); nil; end
|
31
|
+
end
|
32
|
+
|
33
|
+
class String
|
34
|
+
# :nodoc:
|
35
|
+
# Inspired by ActiveSupport
|
36
|
+
def blank?
|
37
|
+
strip.empty?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Symbol
|
42
|
+
# :nodoc:
|
43
|
+
# For Ruby 1.8 compat, inspired by ActiveSupport
|
44
|
+
def to_proc
|
45
|
+
lambda { |o| o.send(self) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
data/lib/tweep/index.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# (c) 2011 Christophe Porteneuve
|
5
|
+
|
6
|
+
module Tweep
|
7
|
+
class Index
|
8
|
+
FILE_NAME = 'tweeping.idx'
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@states = {}
|
12
|
+
@states = YAML::load(File.read(FILE_NAME)) if File.exists?(FILE_NAME)
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_retweet_in!(nick, retweeter, wait)
|
16
|
+
((@states[nick] ||= {})[:waits] ||= {})[retweeter] = wait
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_tweet_index(nick)
|
20
|
+
(@states[nick] ||= {})[:next].to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def retweet_timely?(nick, retweeter)
|
24
|
+
((@states[nick] ||= {})[:waits] ||= {})[retweeter].to_i.zero?
|
25
|
+
end
|
26
|
+
|
27
|
+
def retweet_will_wait!(nick, retweeter)
|
28
|
+
wait = ((@states[nick] ||= {})[:waits] ||= {})[retweeter].to_i
|
29
|
+
@states[nick][:waits][retweeter] = [wait - 1, 0].max
|
30
|
+
end
|
31
|
+
|
32
|
+
def save!
|
33
|
+
File.open(FILE_NAME, 'w') { |f| f.write(YAML::dump(@states)) }
|
34
|
+
rescue Exception => e
|
35
|
+
Tweep.error "Could not save index to #{FILE_NAME} (#{e.class.name}: #{e.message})"
|
36
|
+
end
|
37
|
+
|
38
|
+
def tweeted!(nick, idx)
|
39
|
+
(@states[nick] ||= {})[:next] = idx + 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# Tweep - Automatic Twitter Peeping
|
5
|
+
# Lets me rotate through tweets in a scheduled manner,
|
6
|
+
# with multiple accounts, and auto-retweet such tweets on other accounts yet.
|
7
|
+
#
|
8
|
+
# (c) 2011 Christophe Porteneuve
|
9
|
+
|
10
|
+
require 'logger'
|
11
|
+
|
12
|
+
module Tweep
|
13
|
+
%w(error warn info).each do |level|
|
14
|
+
module_eval <<-EOC
|
15
|
+
def self.#{level}(*args)
|
16
|
+
logger.#{level}(*args)
|
17
|
+
end
|
18
|
+
EOC
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.logger
|
22
|
+
return @logger if @logger
|
23
|
+
@logger = ARGV.include?('--log') ?
|
24
|
+
Logger.new('tweep.log', 5, 1_024 ** 2) :
|
25
|
+
Logger.new(STDOUT)
|
26
|
+
@logger.formatter = proc { |sev, datetime, progname, msg|
|
27
|
+
"[Tweep #{datetime} #{sev.to_s.upcase}] #{msg}\n"
|
28
|
+
}
|
29
|
+
@logger
|
30
|
+
end
|
31
|
+
|
32
|
+
private_class_method :logger
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tweep
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christophe Porteneuve
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-12-13 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: twitter
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.0.2
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: twitter
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.0.2
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
description: Tweep lets you rotate through tweets in a scheduled manner, with multiple accounts, and auto-retweet such tweets on other accounts yet. For instance, think recent accounts for product launches versus their more established company accounts.
|
38
|
+
email: tdd@tddsworld.com
|
39
|
+
executables:
|
40
|
+
- tweep
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- lib/tweep/account.rb
|
47
|
+
- lib/tweep/config.rb
|
48
|
+
- lib/tweep/core_exts.rb
|
49
|
+
- lib/tweep/index.rb
|
50
|
+
- lib/tweep/logging.rb
|
51
|
+
- lib/tweep.rb
|
52
|
+
- MIT-LICENSE.txt
|
53
|
+
- CHANGELOG.markdown
|
54
|
+
- README.markdown
|
55
|
+
- accounts/your_account_1.yml
|
56
|
+
- bin/tweep
|
57
|
+
homepage: http://github.com/tdd/tweep
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.8.10
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Automatic Twitter Peeping
|
84
|
+
test_files: []
|
85
|
+
|