tc-twitterer 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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +135 -0
- data/Rakefile +2 -0
- data/bin/twitterer +34 -0
- data/etc/config.toml.example +30 -0
- data/lib/tc/twitterer/version.rb +5 -0
- data/lib/tc/twitterer.rb +288 -0
- data/tc-twitterer.gemspec +28 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6547466a301c154b30cb6ddbd3078659001b0713
|
4
|
+
data.tar.gz: ea5b46ce6cfac39b611823a22d7eeabe50a04518
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 308afacfbf5f1a571690d17f762049b1c9cc5dc53cb6e5a78a9a4508d8d0976315d9578092006b1792d5bea29bb43d8c2eb48c5e2ed7b15d7886b69c734efa8f
|
7
|
+
data.tar.gz: 5c38dd3ea1e701b24d3f2435eece396126a471d25476d97b24b1aff9acc1a4ab7893edce6ea24791589e408d4414ba6a44022d806b820571b55ac776d65bca9c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 tomonocle
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# twitterer
|
2
|
+
Program for tweeting random lines from files hosted on a public GitHub repo.
|
3
|
+
|
4
|
+
## Purpose
|
5
|
+
I wanted an easy way to drip-feed my [Miscellany](https://github.com/tomonocle/miscellany/)
|
6
|
+
files into the ether, so wrote `twitterer` to do just that.
|
7
|
+
|
8
|
+
Essentially you feed it paths to publicly accessible files on GitHub and it'll
|
9
|
+
tweet a random line from them (one tweet per file) with a link to the line on
|
10
|
+
GitHub. Optionally it can keep track of previous tweets to avoid sounding
|
11
|
+
repetitive.
|
12
|
+
|
13
|
+
Note: source files must be in the default branch, but `twitterer` will resolve
|
14
|
+
that branch to a commit before tweeting the link.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
```
|
18
|
+
$ gem install tc-twitterer
|
19
|
+
```
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
`twitterer` requires a configuration file to operate. An example is available on
|
23
|
+
[GitHub](https://github.com/tomonocle/tc-twitterer/blob/master/etc/config.toml.example),
|
24
|
+
or you can follow the guide below.
|
25
|
+
|
26
|
+
### Twitter Consumer API key and secret
|
27
|
+
First you need to register a new application with Twitter.
|
28
|
+
|
29
|
+
Visit [https://apps.twitter.com/app/new](https://apps.twitter.com/app/new) and follow the steps.
|
30
|
+
|
31
|
+
Once you've created the app, hit _Keys and Access Tokens_ to get your... Keys
|
32
|
+
and access tokens.
|
33
|
+
|
34
|
+
Config entries:
|
35
|
+
|
36
|
+
```
|
37
|
+
twitter_consumer_key = "012a3AbcBdefg4ChiDjklEmnF"
|
38
|
+
twitter_consumer_secret = "AaBCDEbFGcdHefIgJKhi0jLk1MNOlm2nPQoRS3pTqU4rsVtWuv"
|
39
|
+
```
|
40
|
+
|
41
|
+
### Twitter User API key and secret
|
42
|
+
On the _Keys and Access Tokens_ page for your app, scroll down to _Your Access
|
43
|
+
Token_ and hit _Create my access token_ to create your access token.
|
44
|
+
|
45
|
+
Config entries:
|
46
|
+
|
47
|
+
```
|
48
|
+
twitter_access_token = "012345678901234567-abAcBdeCDfEghFi0GHIJKjk9l1Lmnop"
|
49
|
+
twitter_access_token_secret = "a0AbcB1C2dDEFeGH7If3ghJK4Li15j678kMNlO9PQRmno"
|
50
|
+
```
|
51
|
+
|
52
|
+
### History file
|
53
|
+
`Twitterer` can track its past tweets if you tell it where to store them, which
|
54
|
+
prevents it from repeating itself.
|
55
|
+
|
56
|
+
Config entry:
|
57
|
+
|
58
|
+
```
|
59
|
+
history_file = "/path/to/file"
|
60
|
+
```
|
61
|
+
|
62
|
+
Note: `twitterer` will create the file, but it won't create any directories.
|
63
|
+
|
64
|
+
Note 2: history is stored in a CSV in the following format, which lets it
|
65
|
+
double as a log.
|
66
|
+
|
67
|
+
```
|
68
|
+
timestamp,source,line
|
69
|
+
```
|
70
|
+
|
71
|
+
### Sources
|
72
|
+
Finally you need to specify the locations of the files you want to tweet from.
|
73
|
+
|
74
|
+
The following conditions apply:
|
75
|
+
|
76
|
+
- Must be hosted on GitHub
|
77
|
+
- Must be publicly accessible
|
78
|
+
- Must be in the default branch
|
79
|
+
|
80
|
+
Paths are specified in the following format: `username/repo/path/to/file.txt`
|
81
|
+
|
82
|
+
Config entry:
|
83
|
+
|
84
|
+
```
|
85
|
+
source = [
|
86
|
+
"tomonocle/tc-twitterer/README.md",
|
87
|
+
"tomonocle/trello-list2card/README.md",
|
88
|
+
]
|
89
|
+
```
|
90
|
+
|
91
|
+
### Final config file
|
92
|
+
|
93
|
+
```
|
94
|
+
twitter_consumer_key = "012a3AbcBdefg4ChiDjklEmnF"
|
95
|
+
twitter_consumer_secret = "AaBCDEbFGcdHefIgJKhi0jLk1MNOlm2nPQoRS3pTqU4rsVtWuv"
|
96
|
+
|
97
|
+
twitter_access_token = "012345678901234567-abAcBdeCDfEghFi0GHIJKjk9l1Lmnop"
|
98
|
+
twitter_access_token_secret = "a0AbcB1C2dDEFeGH7If3ghJK4Li15j678kMNlO9PQRmno"
|
99
|
+
|
100
|
+
history_file = "./var/twitterer.history"
|
101
|
+
|
102
|
+
source = [
|
103
|
+
"tomonocle/tc-twitterer/README.md",
|
104
|
+
"tomonocle/trello-list2card/README.md",
|
105
|
+
]
|
106
|
+
```
|
107
|
+
|
108
|
+
## Usage
|
109
|
+
|
110
|
+
```
|
111
|
+
$ twitterer -c config.toml
|
112
|
+
```
|
113
|
+
|
114
|
+
This will pull down each file in the _source_ list, pick a suitable line then tweet it in the following format:
|
115
|
+
|
116
|
+
```
|
117
|
+
<first N characters of line> <url>
|
118
|
+
```
|
119
|
+
|
120
|
+
Where `N` is defined as the maximum tweet length (which will _forever_ be 140 chars) minus the maximum URL length
|
121
|
+
from `t.co` (23 as of December 2017). 140-23 = **117**
|
122
|
+
|
123
|
+
Log output goes to `STDERR`.
|
124
|
+
|
125
|
+
### Logging
|
126
|
+
Log level can be adjusted with `-l [debug,info,warn,error,fatal]` (defaults to **warn**).
|
127
|
+
|
128
|
+
### Dry-run
|
129
|
+
Dry-run (read-only, don't tweet) can be enabled with `-d`. Note: you'll probably want to increase the log level with `-l` for this to be useful.
|
130
|
+
|
131
|
+
### Exit codes
|
132
|
+
| Code | Meaning |
|
133
|
+
|:----:|---------|
|
134
|
+
| 0 | Success (wrote output, or nothing to do) |
|
135
|
+
| 1 | Failure. Something went wrong. Always accompanied by a FATAL log message. |
|
data/Rakefile
ADDED
data/bin/twitterer
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tc/twitterer'
|
5
|
+
|
6
|
+
# twitterer -c <config> -l <log-level> -d <dry-run>
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = 'Usage: twitterer -c <config> [options]'
|
11
|
+
|
12
|
+
opts.on( '-c', '--config <config>', 'Path to config file' ) {
|
13
|
+
|c|
|
14
|
+
options[ :config ] = c
|
15
|
+
}
|
16
|
+
|
17
|
+
opts.on( '-l', '--log-level <level>', 'Specify log level (debug,info,warn,error,fatal)' ) {
|
18
|
+
|l|
|
19
|
+
options[ :log_level ] = l
|
20
|
+
}
|
21
|
+
|
22
|
+
opts.on( '-d', '--dry-run', 'Dry run - don\'t change anything' ) {
|
23
|
+
options[ :dry_run ] = true
|
24
|
+
}
|
25
|
+
|
26
|
+
opts.on_tail( '-h', '--help', 'Display help' ) {
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
}
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
twitterer = TC::Twitterer.new( options[ :config ], options[ :log_level ], options[ :dry_run ] )
|
33
|
+
|
34
|
+
twitterer.run
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# log_level [OPT]
|
2
|
+
# The log level to operate at: debug/info/warn/error/fatal
|
3
|
+
# log_level = "warn"
|
4
|
+
|
5
|
+
# twitter_consumer_key [REQ]
|
6
|
+
# Twitter API key for your application (see README.md)
|
7
|
+
twitter_consumer_key = ""
|
8
|
+
|
9
|
+
# twitter_consumer_secret [REQ]
|
10
|
+
# Twitter API secret for your application (see README.md)
|
11
|
+
twitter_consumer_secret = ""
|
12
|
+
|
13
|
+
# twitter_access_token [REQ]
|
14
|
+
# Twitter API key for your user (see README.md)
|
15
|
+
twitter_access_token = ""
|
16
|
+
|
17
|
+
# twitter_access_token_secret [REQ]
|
18
|
+
# Twitter API secret for your user (see README.md)
|
19
|
+
twitter_access_token_secret = ""
|
20
|
+
|
21
|
+
# history_file [OPT]
|
22
|
+
# path to store previous tweets to avoid being repetitive
|
23
|
+
# history_file = "./var/twitterer.history"
|
24
|
+
|
25
|
+
# source [REQ]
|
26
|
+
# array of GitHub-hosted source files to generate tweets from
|
27
|
+
# must be on the default branch
|
28
|
+
# e.g
|
29
|
+
# source = [ "tomonocle/tc-twitterer/README.md", "tomonocle/trello-list2card/README.md" ]
|
30
|
+
source = []
|
data/lib/tc/twitterer.rb
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
module TC
|
2
|
+
class Twitterer
|
3
|
+
require 'toml'
|
4
|
+
require 'tc/twitterer/version'
|
5
|
+
require 'net/http'
|
6
|
+
require 'json'
|
7
|
+
require 'twitter'
|
8
|
+
require 'redcarpet'
|
9
|
+
require 'redcarpet/render_strip'
|
10
|
+
require 'logger'
|
11
|
+
require 'ostruct'
|
12
|
+
require 'csv'
|
13
|
+
|
14
|
+
MAX_TWEET_LENGTH = 140
|
15
|
+
MAX_URL_LENGTH = 23
|
16
|
+
MAX_STRING_LENGTH = MAX_TWEET_LENGTH - MAX_URL_LENGTH
|
17
|
+
MAX_HISTORY_LENGTH = MAX_STRING_LENGTH
|
18
|
+
|
19
|
+
LOG_LEVEL_MAP = {
|
20
|
+
'debug' => Logger::DEBUG,
|
21
|
+
'info' => Logger::INFO,
|
22
|
+
'warn' => Logger::WARN,
|
23
|
+
'error' => Logger::ERROR,
|
24
|
+
'fatal' => Logger::FATAL,
|
25
|
+
}
|
26
|
+
|
27
|
+
DEFAULT_LOG_LEVEL = 'warn'
|
28
|
+
|
29
|
+
def initialize( config_path, log_level, dry_run )
|
30
|
+
@log = Logger.new( STDERR )
|
31
|
+
self.set_log_level( DEFAULT_LOG_LEVEL )
|
32
|
+
|
33
|
+
@log.info 'Starting up'
|
34
|
+
|
35
|
+
begin
|
36
|
+
fail 'config file not specified' unless config_path
|
37
|
+
fail 'config file not found' unless File.file?( config_path )
|
38
|
+
|
39
|
+
@log.info "Loading config from #{ config_path }"
|
40
|
+
@config = OpenStruct.new( TOML.load_file( config_path ) )
|
41
|
+
@log.info 'Loaded config'
|
42
|
+
|
43
|
+
rescue => e
|
44
|
+
@log.fatal "Failed to load config: #{e.message}"
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
|
48
|
+
self.set_log_level( @config[ 'log_level' ] )
|
49
|
+
self.set_log_level( log_level )
|
50
|
+
|
51
|
+
# very basic sanity check of the config
|
52
|
+
[ 'twitter_consumer_key', 'twitter_consumer_secret', 'twitter_access_token', 'twitter_access_token_secret', 'source' ].each do |key|
|
53
|
+
unless @config[ key ]
|
54
|
+
@log.fatal "Required key '#{ key } not present in config"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
@twitter = Twitter::REST::Client.new do |config|
|
61
|
+
config.consumer_key = @config.twitter_consumer_key
|
62
|
+
config.consumer_secret = @config.twitter_consumer_secret
|
63
|
+
config.access_token = @config.twitter_access_token
|
64
|
+
config.access_token_secret = @config.twitter_access_token_secret
|
65
|
+
end
|
66
|
+
|
67
|
+
@log.info "Connected to twitter as '#{@twitter.user.screen_name}'"
|
68
|
+
|
69
|
+
rescue => e
|
70
|
+
@log.fatal "Failed to connect to twitter: #{e.message}"
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
|
74
|
+
# open and import the history if configured
|
75
|
+
if @config.history_file
|
76
|
+
@log.info( "Loading history from '#{ @config.history_file }'" )
|
77
|
+
@history = {}
|
78
|
+
|
79
|
+
begin
|
80
|
+
unless File.file?( @config.history_file )
|
81
|
+
@log.info 'History not present - creating'
|
82
|
+
File.write( @config.history_file, nil )
|
83
|
+
end
|
84
|
+
|
85
|
+
CSV.foreach( @config.history_file ) do |csv|
|
86
|
+
timestamp, source, line = csv
|
87
|
+
|
88
|
+
@log.debug( "Processing history: #{source}/#{line}/#{timestamp}")
|
89
|
+
|
90
|
+
# apparently ruby doesn't autovivify? Vive la perl!
|
91
|
+
@history[source] ||= {}
|
92
|
+
@history[source][line] = timestamp
|
93
|
+
end
|
94
|
+
|
95
|
+
rescue => e
|
96
|
+
@log.fatal( "Failed to import history from '#{ @config.history_file }': #{ e }" )
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if dry_run
|
102
|
+
@log.warn 'Dry run mode: ACTIVATED'
|
103
|
+
@dry_run = true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_log_level( level )
|
108
|
+
return unless level
|
109
|
+
|
110
|
+
level.downcase!
|
111
|
+
|
112
|
+
unless LOG_LEVEL_MAP[ level ]
|
113
|
+
@log.fatal "Unrecognised log_level '#{ level }'"
|
114
|
+
exit 1
|
115
|
+
end
|
116
|
+
|
117
|
+
@log.level = LOG_LEVEL_MAP[ level ]
|
118
|
+
end
|
119
|
+
|
120
|
+
def resolve_repo( repo )
|
121
|
+
@log.info "Resolving default->hash for '#{repo}'"
|
122
|
+
|
123
|
+
begin
|
124
|
+
response = Net::HTTP.get_response( URI( "https://api.github.com/repos/#{repo}" ) )
|
125
|
+
|
126
|
+
# fail if not 200 OK
|
127
|
+
response.value
|
128
|
+
|
129
|
+
json = JSON.parse( response.body )
|
130
|
+
|
131
|
+
default_branch = json[ 'default_branch' ]
|
132
|
+
@log.info "Found default branch '#{default_branch}'"
|
133
|
+
|
134
|
+
response = Net::HTTP.get_response( URI( "https://api.github.com/repos/#{repo}/git/refs/heads/#{ default_branch }" ) )
|
135
|
+
|
136
|
+
# this will fail unless we get a 200 OK
|
137
|
+
response.value
|
138
|
+
|
139
|
+
json = JSON.parse( response.body )
|
140
|
+
|
141
|
+
rescue => e
|
142
|
+
@log.error "Failed to resolve '#{repo}' default '#{default_branch}': #{e}"
|
143
|
+
raise e
|
144
|
+
end
|
145
|
+
|
146
|
+
hash = json['object']['sha']
|
147
|
+
|
148
|
+
@log.debug "Resolved #{default_branch}->#{hash} for '#{repo}'"
|
149
|
+
|
150
|
+
hash
|
151
|
+
end
|
152
|
+
|
153
|
+
def fetch_file( repo, hash, path )
|
154
|
+
@log.info "Fetching '#{repo}/#{path}' at '#{hash}'"
|
155
|
+
|
156
|
+
begin
|
157
|
+
response = Net::HTTP.get_response( URI( "https://raw.githubusercontent.com/#{repo}/#{hash}/#{path}" ) )
|
158
|
+
|
159
|
+
# this will fail unless we get a 200 OK
|
160
|
+
response.value
|
161
|
+
|
162
|
+
rescue => e
|
163
|
+
@log.error "Failed to fetch '#{repo}/#{path}' at '#{hash}': #{e}"
|
164
|
+
raise e
|
165
|
+
end
|
166
|
+
|
167
|
+
@log.debug "Fetched '#{repo}/#{path}' at '#{hash}'"
|
168
|
+
|
169
|
+
response.body
|
170
|
+
end
|
171
|
+
|
172
|
+
def pick_line( repo, path, contents )
|
173
|
+
n = 0
|
174
|
+
pick = ''
|
175
|
+
rows = contents.split( "\n" )
|
176
|
+
|
177
|
+
source = "#{repo}/#{path}"
|
178
|
+
|
179
|
+
@log.info "Picking suitable line from '#{source}'"
|
180
|
+
|
181
|
+
for i in 1..rows.count
|
182
|
+
line_number = rand( rows.count )
|
183
|
+
line = rows[ line_number ]
|
184
|
+
|
185
|
+
# must contain an alpha - don't bother logging as this is specified behavior
|
186
|
+
next unless line =~ /[a-zA-Z]/
|
187
|
+
|
188
|
+
# mustn't've been used before
|
189
|
+
if @config.history_file
|
190
|
+
key = line[ 0 .. MAX_HISTORY_LENGTH ]
|
191
|
+
if @history.key?( source ) and @history[ source ].key?( key )
|
192
|
+
@log.debug "Skipping '#{line}' because we've used it before (#{ @history[ source ][ key ] })"
|
193
|
+
next
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# if we're here, we're good to go
|
198
|
+
pick = line
|
199
|
+
n = line_number + 1
|
200
|
+
|
201
|
+
break
|
202
|
+
end
|
203
|
+
|
204
|
+
if n == 0
|
205
|
+
fail "Failed to pick an entry from '#{source}' - exhausted content?"
|
206
|
+
end
|
207
|
+
|
208
|
+
@log.debug "Picked '#{pick}' [#{n}] from '#{source}'"
|
209
|
+
|
210
|
+
return pick, n
|
211
|
+
end
|
212
|
+
|
213
|
+
def sanitise( line )
|
214
|
+
rc = Redcarpet::Markdown.new( Redcarpet::Render::StripDown )
|
215
|
+
|
216
|
+
# remove any markdown
|
217
|
+
line = rc.render( line ).strip!
|
218
|
+
|
219
|
+
# compress any whitespace
|
220
|
+
line.gsub!( /\s+/, ' ' )
|
221
|
+
|
222
|
+
# truncate to a sane length, add an elipsis if necessary
|
223
|
+
line = ( line.length > MAX_STRING_LENGTH ? "#{ line[ 0 .. MAX_STRING_LENGTH ] }..." : line )
|
224
|
+
|
225
|
+
# just in case we truncated after a space
|
226
|
+
line.gsub!( /\s\.\.\./, '...' )
|
227
|
+
|
228
|
+
line
|
229
|
+
end
|
230
|
+
|
231
|
+
def tweet( repo, hash, path, line, line_number )
|
232
|
+
link = "https://github.com/#{repo}/blame/#{hash}/#{path}#L#{line_number}"
|
233
|
+
|
234
|
+
tweet = sprintf '%s %s', sanitise( line ), link
|
235
|
+
|
236
|
+
@log.warn sprintf "%sTweeting '%s' [%d] from %s/%s", ( @dry_run == true ? '[DRYRUN] ' : '' ), tweet, tweet.length, repo, path
|
237
|
+
@twitter.update( tweet ) unless @dry_run
|
238
|
+
end
|
239
|
+
|
240
|
+
def update_history( source, line )
|
241
|
+
# limit the length to avoid bloat in the history file
|
242
|
+
line = line[ 0 .. MAX_HISTORY_LENGTH ]
|
243
|
+
|
244
|
+
@log.info sprintf "%sAdding '%s' to history for '%s'", ( @dry_run == true ? '[DRYRUN] ' : '' ), line, source
|
245
|
+
return if @dry_run
|
246
|
+
|
247
|
+
CSV.open( @config.history_file, 'a' ) do |csv|
|
248
|
+
csv << [ Time.now.strftime( '%FT%T%z' ), source, line ]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def run
|
253
|
+
@config.source.each do |source|
|
254
|
+
@log.info "Processing '#{source}'"
|
255
|
+
|
256
|
+
begin
|
257
|
+
if m = source.match( %r{(.*?/.*?)/(.*)} )
|
258
|
+
repo, path = m.captures
|
259
|
+
else
|
260
|
+
fail "Couldn't extract repo and path from '#{source}' - skipping"
|
261
|
+
next
|
262
|
+
end
|
263
|
+
|
264
|
+
# convert default->hash
|
265
|
+
hash = resolve_repo( repo )
|
266
|
+
|
267
|
+
# fetch file
|
268
|
+
file_body = fetch_file( repo, hash, path )
|
269
|
+
|
270
|
+
# extract suitable line
|
271
|
+
line, line_number = pick_line( repo, path, file_body )
|
272
|
+
|
273
|
+
# generate the tweet and send it
|
274
|
+
tweet( repo, hash, path, line, line_number )
|
275
|
+
|
276
|
+
# store in history
|
277
|
+
if @config.history_file
|
278
|
+
update_history( source, line )
|
279
|
+
end
|
280
|
+
|
281
|
+
# all done!
|
282
|
+
rescue => e
|
283
|
+
@log.error "Failed '#{source}': #{e}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tc/twitterer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'tc-twitterer'
|
8
|
+
spec.version = TC::Twitterer::VERSION
|
9
|
+
spec.authors = ['tomonocle']
|
10
|
+
spec.email = ['github@woot.co.uk']
|
11
|
+
|
12
|
+
spec.summary = 'Program for tweeting random lines from files hosted on a public GitHub repo'
|
13
|
+
spec.description = 'This program pulls a specified file from a GitHub repo, then tweets a random line with a link to the line on GitHub'
|
14
|
+
spec.homepage = 'https://github.com/tomonocle/tc-twitterer'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'twitter', '~> 6.2'
|
23
|
+
spec.add_dependency 'toml', '~> 0.2'
|
24
|
+
spec.add_dependency 'redcarpet', '~> 3.4'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tc-twitterer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- tomonocle
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: twitter
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: toml
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redcarpet
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: This program pulls a specified file from a GitHub repo, then tweets a
|
84
|
+
random line with a link to the line on GitHub
|
85
|
+
email:
|
86
|
+
- github@woot.co.uk
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/twitterer
|
97
|
+
- etc/config.toml.example
|
98
|
+
- lib/tc/twitterer.rb
|
99
|
+
- lib/tc/twitterer/version.rb
|
100
|
+
- tc-twitterer.gemspec
|
101
|
+
homepage: https://github.com/tomonocle/tc-twitterer
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.4.5
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Program for tweeting random lines from files hosted on a public GitHub repo
|
125
|
+
test_files: []
|