tweep 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|