twterm 1.0.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 +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
|