twterm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +88 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +10 -0
- data/Rakefile +1 -0
- data/bin/twterm +5 -0
- data/lib/extentions.rb +51 -0
- data/lib/twterm/app.rb +45 -0
- data/lib/twterm/auth.rb +23 -0
- data/lib/twterm/client.rb +214 -0
- data/lib/twterm/color_manager.rb +50 -0
- data/lib/twterm/config.rb +37 -0
- data/lib/twterm/list.rb +20 -0
- data/lib/twterm/notification/base.rb +24 -0
- data/lib/twterm/notification/error.rb +19 -0
- data/lib/twterm/notification/message.rb +19 -0
- data/lib/twterm/notifier.rb +68 -0
- data/lib/twterm/screen.rb +53 -0
- data/lib/twterm/status.rb +102 -0
- data/lib/twterm/tab/auto_reloadable.rb +14 -0
- data/lib/twterm/tab/base.rb +41 -0
- data/lib/twterm/tab/conversation_tab.rb +31 -0
- data/lib/twterm/tab/exceptions.rb +6 -0
- data/lib/twterm/tab/favorites.rb +7 -0
- data/lib/twterm/tab/list_tab.rb +33 -0
- data/lib/twterm/tab/mentions_tab.rb +36 -0
- data/lib/twterm/tab/new/list.rb +88 -0
- data/lib/twterm/tab/new/search.rb +52 -0
- data/lib/twterm/tab/new/start.rb +67 -0
- data/lib/twterm/tab/scrollable.rb +130 -0
- data/lib/twterm/tab/search_tab.rb +29 -0
- data/lib/twterm/tab/statuses_tab.rb +235 -0
- data/lib/twterm/tab/timeline_tab.rb +33 -0
- data/lib/twterm/tab/user_tab.rb +29 -0
- data/lib/twterm/tab_manager.rb +111 -0
- data/lib/twterm/tweetbox.rb +53 -0
- data/lib/twterm/user.rb +40 -0
- data/lib/twterm/user_window.rb +71 -0
- data/lib/twterm/version.rb +3 -0
- data/lib/twterm.rb +50 -0
- data/twterm.gemspec +28 -0
- metadata +184 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e40202371311a827db240194d0b8bf6557116ae1
|
4
|
+
data.tar.gz: 626203d268f804937122696f04ab2200e1f8dbd5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 59b2bceeb082f670e52f639dca1ffc1411cb1ccc6b6a8371806c8acdb259d0af5474bbf5d09d38706dbe821e1d0d2f90518726682d860db8c49901c8977a067c
|
7
|
+
data.tar.gz: 2489774aa2fabd526a67aa15b440cb0befa2f01372991ead8267d24bcfa1f19cf42739a67ac09ed35dd69dfad93acf351865526be6fc4080bb3c2b6760cdf2f9
|
data/.gitignore
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Created by https://www.gitignore.io
|
2
|
+
|
3
|
+
### OSX ###
|
4
|
+
.DS_Store
|
5
|
+
.AppleDouble
|
6
|
+
.LSOverride
|
7
|
+
|
8
|
+
# Icon must end with two \r
|
9
|
+
Icon
|
10
|
+
|
11
|
+
|
12
|
+
# Thumbnails
|
13
|
+
._*
|
14
|
+
|
15
|
+
# Files that might appear on external disk
|
16
|
+
.Spotlight-V100
|
17
|
+
.Trashes
|
18
|
+
|
19
|
+
# Directories potentially created on remote AFP share
|
20
|
+
.AppleDB
|
21
|
+
.AppleDesktop
|
22
|
+
Network Trash Folder
|
23
|
+
Temporary Items
|
24
|
+
.apdisk
|
25
|
+
|
26
|
+
|
27
|
+
### Ruby ###
|
28
|
+
*.gem
|
29
|
+
*.rbc
|
30
|
+
/.config
|
31
|
+
/coverage/
|
32
|
+
/InstalledFiles
|
33
|
+
/pkg/
|
34
|
+
/spec/reports/
|
35
|
+
/test/tmp/
|
36
|
+
/test/version_tmp/
|
37
|
+
/tmp/
|
38
|
+
|
39
|
+
## Specific to RubyMotion:
|
40
|
+
.dat*
|
41
|
+
.repl_history
|
42
|
+
build/
|
43
|
+
|
44
|
+
## Documentation cache and generated files:
|
45
|
+
/.yardoc/
|
46
|
+
/_yardoc/
|
47
|
+
/doc/
|
48
|
+
/rdoc/
|
49
|
+
|
50
|
+
## Environment normalisation:
|
51
|
+
/.bundle/
|
52
|
+
/vendor/bundle/
|
53
|
+
/lib/bundler/man/
|
54
|
+
|
55
|
+
# for a library or gem, you might want to ignore these files since the code is
|
56
|
+
# intended to run in multiple environments; otherwise, check them in:
|
57
|
+
# Gemfile.lock
|
58
|
+
# .ruby-version
|
59
|
+
# .ruby-gemset
|
60
|
+
|
61
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
62
|
+
.rvmrc
|
63
|
+
|
64
|
+
|
65
|
+
### vim ###
|
66
|
+
[._]*.s[a-w][a-z]
|
67
|
+
[._]s[a-w][a-z]
|
68
|
+
*.un~
|
69
|
+
Session.vim
|
70
|
+
.netrwhist
|
71
|
+
*~
|
72
|
+
|
73
|
+
|
74
|
+
### Tags ###
|
75
|
+
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
|
76
|
+
TAGS
|
77
|
+
!TAGS/
|
78
|
+
tags
|
79
|
+
!tags/
|
80
|
+
gtags.files
|
81
|
+
GTAGS
|
82
|
+
GRTAGS
|
83
|
+
GPATH
|
84
|
+
cscope.files
|
85
|
+
cscope.out
|
86
|
+
cscope.in.out
|
87
|
+
cscope.po.out
|
88
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Ryota Kameoka
|
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
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/twterm
ADDED
data/lib/extentions.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
class String
|
2
|
+
def width
|
3
|
+
each_char.map { |c| c.bytesize == 1 ? 1 : 2 }.reduce(0, &:+)
|
4
|
+
end
|
5
|
+
|
6
|
+
def split_by_width(width)
|
7
|
+
cnt = 0
|
8
|
+
str = ''
|
9
|
+
chunks = []
|
10
|
+
|
11
|
+
each_char do |c|
|
12
|
+
if c == "\n"
|
13
|
+
chunks << str
|
14
|
+
str = ''
|
15
|
+
cnt = 0
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
cnt += c.width
|
20
|
+
if cnt > width
|
21
|
+
chunks << str
|
22
|
+
str = ''
|
23
|
+
cnt = 0
|
24
|
+
end
|
25
|
+
str << c unless str.empty? && c == ' '
|
26
|
+
end
|
27
|
+
chunks << str unless str.empty?
|
28
|
+
chunks
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Integer
|
33
|
+
def format
|
34
|
+
to_s.gsub(/(\d)(?=(\d{3})+(?!\d))/, '\1,')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Curses::Window
|
39
|
+
def bold(&block)
|
40
|
+
attron(Curses::A_BOLD)
|
41
|
+
block.call
|
42
|
+
attroff(Curses::A_BOLD)
|
43
|
+
end
|
44
|
+
|
45
|
+
def with_color(fg, bg = :black, &block)
|
46
|
+
color_pair_index = Twterm::ColorManager.instance.get_color_pair_index(fg, bg)
|
47
|
+
attron(Curses.color_pair(color_pair_index))
|
48
|
+
block.call
|
49
|
+
attroff(Curses.color_pair(color_pair_index))
|
50
|
+
end
|
51
|
+
end
|
data/lib/twterm/app.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Twterm
|
2
|
+
class App
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
Config.load
|
7
|
+
Auth.authenticate_user if Config[:screen_name].nil?
|
8
|
+
|
9
|
+
Screen.instance
|
10
|
+
|
11
|
+
client = Client.new(Config[:user_id], Config[:screen_name], Config[:access_token], Config[:access_token_secret])
|
12
|
+
|
13
|
+
timeline = Tab::TimelineTab.new(client)
|
14
|
+
TabManager.instance.add_and_show(timeline)
|
15
|
+
|
16
|
+
mentions_tab = Tab::MentionsTab.new(client)
|
17
|
+
TabManager.instance.add(mentions_tab)
|
18
|
+
|
19
|
+
Screen.instance.refresh
|
20
|
+
|
21
|
+
client.stream
|
22
|
+
UserWindow.instance
|
23
|
+
|
24
|
+
reset_interruption_handler
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
Screen.instance.wait
|
29
|
+
Screen.instance.refresh
|
30
|
+
end
|
31
|
+
|
32
|
+
def register_interruption_handler(&block)
|
33
|
+
fail ArgumentError, 'no block given' unless block_given?
|
34
|
+
Signal.trap(:INT) do
|
35
|
+
block.call
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_interruption_handler
|
40
|
+
Signal.trap(:INT) do
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/twterm/auth.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Twterm
|
2
|
+
module Auth
|
3
|
+
def authenticate_user
|
4
|
+
consumer = OAuth::Consumer.new(
|
5
|
+
'vLNSVFgXclBJQJRZ7VLMxL9lA',
|
6
|
+
'OFLKzrepRG2p1hq0nUB9j2S9ndFQoNTPheTpmOY0GYw55jGgS5',
|
7
|
+
site: 'https://api.twitter.com'
|
8
|
+
)
|
9
|
+
request_token = consumer.get_request_token
|
10
|
+
Launchy.open request_token.authorize_url
|
11
|
+
print 'input PIN: '
|
12
|
+
pin = (STDIN.gets || '').strip
|
13
|
+
access_token = request_token.get_access_token(oauth_verifier: pin)
|
14
|
+
|
15
|
+
Config[:access_token] = access_token.token
|
16
|
+
Config[:access_token_secret] = access_token.secret
|
17
|
+
Config[:screen_name] = access_token.params[:screen_name]
|
18
|
+
Config[:user_id] = access_token.params[:user_id]
|
19
|
+
end
|
20
|
+
|
21
|
+
module_function :authenticate_user
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Twterm
|
2
|
+
class Client
|
3
|
+
attr_reader :user_id, :screen_name
|
4
|
+
|
5
|
+
CREATE_STATUS_PROC = -> (s) { Status.new(s) }
|
6
|
+
|
7
|
+
@@instances = []
|
8
|
+
|
9
|
+
def initialize(user_id, screen_name, token, secret)
|
10
|
+
@user_id = user_id
|
11
|
+
@screen_name = screen_name
|
12
|
+
|
13
|
+
@rest_client = Twitter::REST::Client.new do |config|
|
14
|
+
config.consumer_key = 'vLNSVFgXclBJQJRZ7VLMxL9lA'
|
15
|
+
config.consumer_secret = 'OFLKzrepRG2p1hq0nUB9j2S9ndFQoNTPheTpmOY0GYw55jGgS5'
|
16
|
+
config.access_token = token
|
17
|
+
config.access_token_secret = secret
|
18
|
+
end
|
19
|
+
|
20
|
+
TweetStream.configure do |config|
|
21
|
+
config.consumer_key = 'vLNSVFgXclBJQJRZ7VLMxL9lA'
|
22
|
+
config.consumer_secret = 'OFLKzrepRG2p1hq0nUB9j2S9ndFQoNTPheTpmOY0GYw55jGgS5'
|
23
|
+
config.oauth_token = token
|
24
|
+
config.oauth_token_secret = secret
|
25
|
+
config.auth_method = :oauth
|
26
|
+
end
|
27
|
+
|
28
|
+
@stream_client = TweetStream::Client.new
|
29
|
+
|
30
|
+
@callbacks = {}
|
31
|
+
@@instances << self
|
32
|
+
end
|
33
|
+
|
34
|
+
def stream
|
35
|
+
@stream_client.on_timeline_status do |tweet|
|
36
|
+
status = Status.new(tweet)
|
37
|
+
invoke_callbacks(:timeline_status, status)
|
38
|
+
invoke_callbacks(:mention, status) if status.text.include? "@#{@screen_name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
@stream_client.on_delete do |status_id|
|
42
|
+
timeline.delete_status(status_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
@stream_client.on_event(:favorite) do |event|
|
46
|
+
break if event[:source][:screen_name] == @screen_name
|
47
|
+
message = "@#{event[:source][:screen_name]} has favorited your tweet: #{event[:target_object][:text]}"
|
48
|
+
Notifier.instance.show_message(message)
|
49
|
+
end
|
50
|
+
|
51
|
+
@stream_client.on_no_data_received do
|
52
|
+
connect_stream
|
53
|
+
end
|
54
|
+
|
55
|
+
connect_stream
|
56
|
+
end
|
57
|
+
|
58
|
+
def connect_stream
|
59
|
+
@stream_client.stop_stream
|
60
|
+
@streaming_thread.kill if @streaming_thread.is_a? Thread
|
61
|
+
|
62
|
+
Notifier.instance.show_message 'Trying to connect to Twitter...'
|
63
|
+
@streaming_thread = Thread.new do
|
64
|
+
begin
|
65
|
+
@stream_client.userstream
|
66
|
+
rescue EventMachine::ConnectionError
|
67
|
+
Notifier.instance.show_error 'Connection failed'
|
68
|
+
sleep 30
|
69
|
+
retry
|
70
|
+
end
|
71
|
+
Notifier.instance.show_message 'Connection established'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def post(text, in_reply_to = nil)
|
76
|
+
send_request do
|
77
|
+
if in_reply_to.is_a? Status
|
78
|
+
text = "@#{in_reply_to.user.screen_name} #{text}"
|
79
|
+
@rest_client.update(text, in_reply_to_status_id: in_reply_to.id)
|
80
|
+
else
|
81
|
+
@rest_client.update(text)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def home_timeline
|
87
|
+
send_request do
|
88
|
+
yield @rest_client.home_timeline(count: 200).map(&CREATE_STATUS_PROC)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def mentions
|
93
|
+
send_request do
|
94
|
+
yield @rest_client.mentions(count: 200).map(&CREATE_STATUS_PROC)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def user_timeline(user_id)
|
99
|
+
send_request do
|
100
|
+
yield @rest_client.user_timeline(user_id, count: 200).map(&CREATE_STATUS_PROC)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def lists
|
105
|
+
send_request do
|
106
|
+
yield @rest_client.lists.map { |list| List.new(list) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def list(list)
|
111
|
+
fail ArgumentError, 'argument must be an instance of List class' unless list.is_a? List
|
112
|
+
send_request do
|
113
|
+
yield @rest_client.list_timeline(list.id, count: 200).map(&CREATE_STATUS_PROC)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def search(query)
|
118
|
+
send_request do
|
119
|
+
yield @rest_client.search(query, count: 100).map(&CREATE_STATUS_PROC)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def show_status(status_id)
|
124
|
+
send_request do
|
125
|
+
yield Status.new(@rest_client.status(status_id))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def favorite(status)
|
130
|
+
return false unless status.is_a? Status
|
131
|
+
|
132
|
+
send_request do
|
133
|
+
@rest_client.favorite(status.id)
|
134
|
+
status.favorite!
|
135
|
+
yield status if block_given?
|
136
|
+
end
|
137
|
+
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
def unfavorite(status)
|
142
|
+
fail ArgumentError, 'argument must be an instance of Status class' unless status.is_a? Status
|
143
|
+
|
144
|
+
send_request do
|
145
|
+
@rest_client.unfavorite(status.id)
|
146
|
+
status.unfavorite!
|
147
|
+
yield status if block_given?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def retweet(status)
|
152
|
+
return false unless status.is_a? Status
|
153
|
+
|
154
|
+
send_request do
|
155
|
+
begin
|
156
|
+
@rest_client.retweet!(status.id)
|
157
|
+
status.retweet!
|
158
|
+
yield status if block_given?
|
159
|
+
rescue Twitter::Error::AlreadyRetweeted, Twitter::Error::NotFound, Twitter::Error::Forbidden
|
160
|
+
Notifier.instance.show_error 'Retweet attempt failed'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def on_timeline_status(&block)
|
166
|
+
fail ArgumentError, 'no block given' unless block_given?
|
167
|
+
on(:timeline_status, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
def on_mention(&block)
|
171
|
+
fail ArgumentError, 'no block given' unless block_given?
|
172
|
+
on(:mention, &block)
|
173
|
+
end
|
174
|
+
|
175
|
+
class << self
|
176
|
+
def new(user_id, screen_name, token, secret)
|
177
|
+
detector = -> (instance) { instance.user_id == user_id }
|
178
|
+
instance = @@instances.find(&detector)
|
179
|
+
instance.nil? ? super : instance
|
180
|
+
end
|
181
|
+
|
182
|
+
def current
|
183
|
+
@@instances[0]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def on(event, &block)
|
190
|
+
@callbacks[event] ||= []
|
191
|
+
@callbacks[event] << block
|
192
|
+
self
|
193
|
+
end
|
194
|
+
|
195
|
+
def invoke_callbacks(event, data = nil)
|
196
|
+
return if @callbacks[event].nil?
|
197
|
+
|
198
|
+
@callbacks[event].each { |cb| cb.call(data) }
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
def send_request(&block)
|
203
|
+
Thread.new do
|
204
|
+
begin
|
205
|
+
block.call
|
206
|
+
rescue Twitter::Error => e
|
207
|
+
Notifier.instance.show_error 'Failed to send request'
|
208
|
+
sleep 10
|
209
|
+
retry if e.message == 'getaddrinfo: nodename nor servname provided, or not known'
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Twterm
|
2
|
+
class ColorManager
|
3
|
+
include Singleton
|
4
|
+
include Curses
|
5
|
+
|
6
|
+
COLORS = [:black, :white, :red, :green, :blue, :yellow, :cyan, :magenta]
|
7
|
+
CURSES_COLORS = {
|
8
|
+
black: COLOR_BLACK,
|
9
|
+
white: COLOR_WHITE,
|
10
|
+
red: COLOR_RED,
|
11
|
+
green: COLOR_GREEN,
|
12
|
+
blue: COLOR_BLUE,
|
13
|
+
yellow: COLOR_YELLOW,
|
14
|
+
cyan: COLOR_CYAN,
|
15
|
+
magenta: COLOR_MAGENTA
|
16
|
+
}
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@colors = {
|
20
|
+
black: {}, white: {}, red: {}, green: {},
|
21
|
+
blue: {}, yellow: {}, cyan: {}, magenta: {}
|
22
|
+
}
|
23
|
+
@count = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_color_pair_index(fg, bg)
|
27
|
+
fail ArgumentError, 'invalid color name' unless COLORS.include? fg
|
28
|
+
fail ArgumentError, 'invalid color name' unless COLORS.include? bg
|
29
|
+
|
30
|
+
return @colors[bg][fg] unless @colors[bg][fg].nil?
|
31
|
+
|
32
|
+
add_color(fg, bg)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def add_color(fg, bg)
|
38
|
+
fail ArgumentError, 'invalid color name' unless COLORS.include? fg
|
39
|
+
fail ArgumentError, 'invalid color name' unless COLORS.include? bg
|
40
|
+
|
41
|
+
@count += 1
|
42
|
+
index = @count
|
43
|
+
|
44
|
+
Curses.init_pair(index, CURSES_COLORS[fg], CURSES_COLORS[bg])
|
45
|
+
@colors[bg][fg] = index
|
46
|
+
|
47
|
+
index
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Twterm
|
2
|
+
module Config
|
3
|
+
CONFIG_FILE = "#{ENV['HOME']}/.twterm.yml"
|
4
|
+
|
5
|
+
def [](key)
|
6
|
+
@config[key]
|
7
|
+
end
|
8
|
+
|
9
|
+
def []=(key, value)
|
10
|
+
@config[key] = value
|
11
|
+
save
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
unless File.exist? CONFIG_FILE
|
16
|
+
@config = {}
|
17
|
+
return
|
18
|
+
end
|
19
|
+
@config = YAML.load(File.read(CONFIG_FILE)) || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def save
|
25
|
+
begin
|
26
|
+
file = File.open(CONFIG_FILE, 'w', 0600)
|
27
|
+
file.write @config.to_yaml
|
28
|
+
rescue
|
29
|
+
puts 'exception raised'
|
30
|
+
ensure
|
31
|
+
file.close
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module_function :[], :[]=, :load, :save
|
36
|
+
end
|
37
|
+
end
|
data/lib/twterm/list.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Twterm
|
2
|
+
class List
|
3
|
+
attr_reader :id, :name, :slug, :full_name, :mode, :description, :member_count, :subscriber_count
|
4
|
+
|
5
|
+
def initialize(list)
|
6
|
+
@id = list.id
|
7
|
+
@name = list.name
|
8
|
+
@slug = list.slug
|
9
|
+
@full_name = list.full_name
|
10
|
+
@mode = list.mode
|
11
|
+
@description = list.description.is_a?(Twitter::NullObject) ? '' : list.description
|
12
|
+
@member_count = list.member_count
|
13
|
+
@subscriber_count = list.subscriber_count
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
other.is_a?(self.class) && id == other.id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Twterm
|
2
|
+
module Notification
|
3
|
+
module Base
|
4
|
+
attr_reader :time, :fg_color, :bg_color
|
5
|
+
|
6
|
+
def initialize(message)
|
7
|
+
@message = message
|
8
|
+
@time = Time.now
|
9
|
+
end
|
10
|
+
|
11
|
+
def show_with_width(width)
|
12
|
+
@message.gsub("\n", ' ')
|
13
|
+
end
|
14
|
+
|
15
|
+
def fg_color
|
16
|
+
fail NotImplementedError, 'fg_color method must be implemented'
|
17
|
+
end
|
18
|
+
|
19
|
+
def bg_color
|
20
|
+
fail NotImplementedError, 'bg_color method must be implemented'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|