turntabler 0.0.1

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.
Files changed (48) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +7 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +20 -0
  7. data/README.md +383 -0
  8. data/Rakefile +11 -0
  9. data/examples/Gemfile +3 -0
  10. data/examples/Gemfile.lock +29 -0
  11. data/examples/autobop.rb +13 -0
  12. data/examples/autofan.rb +13 -0
  13. data/examples/blacklist.rb +16 -0
  14. data/examples/bop.rb +15 -0
  15. data/examples/bopcount.rb +20 -0
  16. data/examples/chat_bot.rb +16 -0
  17. data/examples/modlist.rb +19 -0
  18. data/examples/switch.rb +40 -0
  19. data/examples/time_afk_list.rb +46 -0
  20. data/lib/turntabler/assertions.rb +36 -0
  21. data/lib/turntabler/authorized_user.rb +217 -0
  22. data/lib/turntabler/avatar.rb +34 -0
  23. data/lib/turntabler/boot.rb +22 -0
  24. data/lib/turntabler/client.rb +457 -0
  25. data/lib/turntabler/connection.rb +176 -0
  26. data/lib/turntabler/digest_helpers.rb +13 -0
  27. data/lib/turntabler/error.rb +5 -0
  28. data/lib/turntabler/event.rb +239 -0
  29. data/lib/turntabler/handler.rb +67 -0
  30. data/lib/turntabler/loggable.rb +11 -0
  31. data/lib/turntabler/message.rb +24 -0
  32. data/lib/turntabler/playlist.rb +50 -0
  33. data/lib/turntabler/preferences.rb +70 -0
  34. data/lib/turntabler/resource.rb +194 -0
  35. data/lib/turntabler/room.rb +377 -0
  36. data/lib/turntabler/room_directory.rb +133 -0
  37. data/lib/turntabler/snag.rb +16 -0
  38. data/lib/turntabler/song.rb +247 -0
  39. data/lib/turntabler/sticker.rb +48 -0
  40. data/lib/turntabler/sticker_placement.rb +25 -0
  41. data/lib/turntabler/user.rb +274 -0
  42. data/lib/turntabler/version.rb +9 -0
  43. data/lib/turntabler/vote.rb +19 -0
  44. data/lib/turntabler.rb +102 -0
  45. data/spec/spec_helper.rb +7 -0
  46. data/spec/turntabler_spec.rb +4 -0
  47. data/turntable.gemspec +24 -0
  48. metadata +173 -0
@@ -0,0 +1,247 @@
1
+ require 'turntabler/error'
2
+ require 'turntabler/resource'
3
+ require 'turntabler/user'
4
+ require 'turntabler/vote'
5
+
6
+ module Turntabler
7
+ # Represents a song that can be played on Turntable
8
+ class Song < Resource
9
+ # The title of the song
10
+ # @return [String]
11
+ attribute :title, :song
12
+
13
+ # The standard code id for this song
14
+ # @return [String]
15
+ attribute :isrc
16
+
17
+ # The name of the artist
18
+ # @return [String]
19
+ attribute :artist
20
+
21
+ # The name of the album this song is on
22
+ # @return [String]
23
+ attribute :album
24
+
25
+ # The type of music
26
+ # @return [String]
27
+ attribute :genre
28
+
29
+ # The label that produced the music
30
+ # @return [String]
31
+ attribute :label
32
+
33
+ # The URL for the cover art image
34
+ # @return [String]
35
+ attribute :cover_art_url, :coverart
36
+
37
+ # Number of seconds the song lasts
38
+ # @return [Fixnum]
39
+ attribute :length
40
+
41
+ # Whether this song can be snagged
42
+ # @return [Boolean]
43
+ attribute :snaggable
44
+
45
+ # The source from which the song was uploaded
46
+ # @return [String]
47
+ attribute :source
48
+
49
+ # The id of the song on the original source service
50
+ # @return [String]
51
+ attribute :source_id, :sourceid
52
+
53
+ # The number of up votes this song has received.
54
+ # @note This is only available for the current song playing in a room
55
+ # @return [Fixnum]
56
+ attribute :up_votes_count, :upvotes, :load => false
57
+
58
+ # The number of down votes this song has received.
59
+ # @note This is only available for the current song playing in a room
60
+ # @return [Fixnum]
61
+ attribute :down_votes_count, :downvotes, :load => false
62
+
63
+ # The log of votes this song has received. This will only include up votes
64
+ # or down votes that were previously up votes.
65
+ # @note This is only available for the current song playing in a room
66
+ # @return [Array<Vote>]
67
+ attribute :votes, :votelog, :load => false do |votes|
68
+ votes.each do |(user_id, direction)|
69
+ self.votes.delete_if {|vote| vote.user.id == user_id}
70
+ self.votes << Vote.new(client, :userid => user_id, :direction => direction)
71
+ end
72
+ self.votes
73
+ end
74
+
75
+ # The percentage score for this song based on the number of votes
76
+ # @note This is only available for the current song playing in a room
77
+ # @return [Float]
78
+ attribute :score, :load => false
79
+
80
+ # The DJ that played this song
81
+ # @note This is only available for the current song playing in a room
82
+ # @return [Turntabler::User]
83
+ attribute :played_by, :djid, :load => false do |value|
84
+ room? ? room.build_user(:_id => value) : User.new(client, :_id => value)
85
+ end
86
+
87
+ # @api private
88
+ def initialize(*)
89
+ @up_votes_count = 0
90
+ @down_votes_count = 0
91
+ @votes = []
92
+ @score = 0
93
+ super
94
+ end
95
+
96
+ # Loads the attributes for this song. Attributes will automatically load
97
+ # when accessed, but this allows data to be forcefully loaded upfront.
98
+ #
99
+ # @return [true]
100
+ # @raise [Turntabler::Error] if the command fails
101
+ # @example
102
+ # song.load # => true
103
+ # song.title # => "..."
104
+ def load
105
+ data = api('playlist.get_metadata', :playlist_name => 'default', :files => [id])
106
+ self.attributes = data['files'][id]
107
+ super
108
+ end
109
+
110
+ # Skips the song.
111
+ #
112
+ # @return [true]
113
+ # @raise [Turntabler::Error] if the command fails
114
+ # @raise [Turntabler::Error] if the song is not playing in the current song
115
+ # @example
116
+ # song.skip # => true
117
+ def skip
118
+ assert_current_song
119
+ api('room.stop_song', :roomid => room.id, :section => room.section)
120
+ true
121
+ end
122
+
123
+ # Vote for the song.
124
+ #
125
+ # @param [Symbol] direction The direction to vote the song (:up or :down)
126
+ # @return [true]
127
+ # @raise [Turntabler::Error] if the command fails
128
+ # @raise [Turntabler::Error] if the song is not playing in the current song
129
+ # @example
130
+ # song.vote # => true
131
+ # song.vote(:down) # => true
132
+ def vote(direction = :up)
133
+ assert_current_song
134
+ api('room.vote',
135
+ :roomid => room.id,
136
+ :section => room.section,
137
+ :val => direction,
138
+ :songid => id,
139
+ :vh => digest("#{room.id}#{direction}#{id}"),
140
+ :th => digest(rand),
141
+ :ph => digest(rand)
142
+ )
143
+ true
144
+ end
145
+
146
+ # Triggers the heart animation for the song.
147
+ #
148
+ # @note This will not add the song to the user's playlist
149
+ # @return [true]
150
+ # @raise [Turntabler::Error] if the command fails
151
+ # @raise [Turntabler::Error] if the song is not playing in the current song
152
+ # @example
153
+ # song.snag # => true
154
+ def snag
155
+ assert_current_song
156
+ sh = digest(rand)
157
+ api('snag.add',
158
+ :djid => room.current_dj.id,
159
+ :songid => id,
160
+ :roomid => room.id,
161
+ :section => room.section,
162
+ :site => 'queue',
163
+ :location => 'board',
164
+ :in_queue => 'false',
165
+ :blocked => 'false',
166
+ :vh => digest([client.user.id, room.current_dj.id, id, room.id, 'queue', 'board', 'false', 'false', sh] * '/'),
167
+ :sh => sh,
168
+ :fh => digest(rand)
169
+ )
170
+ true
171
+ end
172
+
173
+ # Adds the song to one of the user's playlists.
174
+ #
175
+ # @param [Hash] options The options for where to add the song
176
+ # @option options [String] :playlist ("default") The playlist to enqueue the song in
177
+ # @option options [Fixnum] :index (0) The location in the playlist to insert the song
178
+ # @return [true]
179
+ # @raise [ArgumentError] if an invalid option is specified
180
+ # @raise [Turntabler::Error] if the command fails
181
+ # @example
182
+ # song.enqueue(:index => 1) # => true
183
+ def enqueue(options = {})
184
+ assert_valid_keys(options, :playlist, :index)
185
+ options = {:playlist => 'default', :index => 0}.merge(options)
186
+ playlist, index = client.user.playlist(options[:playlist]), options[:index]
187
+
188
+ api('playlist.add', :playlist_name => playlist.id, :song_dict => {:fileid => id}, :index => index)
189
+ playlist.songs.insert(index, self) if playlist.loaded?
190
+ true
191
+ end
192
+
193
+ # Removes the song from the playlist at the given index.
194
+ #
195
+ # @param [Hash] options The options for where to remove the song
196
+ # @option options [String] :playlist ("default") The playlist to dequeue the song from
197
+ # @return [true]
198
+ # @raise [ArgumentError] if an invalid option is specified
199
+ # @raise [Turntabler::Error] if the command fails
200
+ # @example
201
+ # song.dequeue # => true
202
+ def dequeue(options = {})
203
+ assert_valid_keys(options, :playlist)
204
+ options = {:playlist => 'default'}.merge(options)
205
+ playlist, index = index(options[:playlist])
206
+
207
+ api('playlist.remove', :playlist_name => playlist.id, :index => index)
208
+ playlist.songs.delete(self)
209
+ true
210
+ end
211
+
212
+ # Move a song from one location in the playlist to another.
213
+ #
214
+ # @param [Fixnum] to_index The index to move the song to
215
+ # @param [Hash] options The options for where to remove the song
216
+ # @option options [String] :playlist ("default") The playlist to move the song within
217
+ # @return [true]
218
+ # @raise [ArgumentError] if an invalid option is specified
219
+ # @raise [Turntabler::Error] if the command fails
220
+ # song.move(5) # => true
221
+ def move(to_index, options = {})
222
+ assert_valid_keys(options, :playlist)
223
+ options = {:playlist => 'default'}.merge(options)
224
+ playlist, index = index(options[:playlist])
225
+
226
+ api('playlist.reorder', :playlist_name => playlist.id, :index_from => index, :index_to => to_index)
227
+ playlist.songs.insert(to_index, playlist.songs.delete(self))
228
+ true
229
+ end
230
+
231
+ private
232
+ # Asserts that this is the song currently being played in the room the user
233
+ # is in. Raises Turntabler::Error if this is not the case.
234
+ def assert_current_song
235
+ raise(Turntabler::Error, "Song \"#{id}\" is not currently playing") unless room.current_song == self
236
+ end
237
+
238
+ # Gets the index of this song within the given playlist. Raises Turntabler::Error
239
+ # if the song cannot be found in the playlist.
240
+ def index(playlist_id)
241
+ playlist = client.user.playlist(playlist_id)
242
+ index = playlist.songs.index(self)
243
+ raise(Turntabler::Error, "Song \"#{id}\" is not in playlist \"#{playlist.id}\"") unless index
244
+ return playlist, index
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,48 @@
1
+ require 'turntabler/resource'
2
+
3
+ module Turntabler
4
+ # Represents a virtual sticker that can be placed on a user
5
+ class Sticker < Resource
6
+ # Allow the id to be set via the "sticker_id" attribute
7
+ # @return [String]
8
+ attribute :id, :sticker_id
9
+
10
+ # The human-readable name for the sticker
11
+ # @return [String]
12
+ attribute :name
13
+
14
+ # A longer explanation for the sticker
15
+ # @return [String]
16
+ attribute :description
17
+
18
+ # The type of sticker (such as "laptop_sticker")
19
+ # @return [String]
20
+ attribute :category
21
+
22
+ # How much it costs to purchase this sticker for use
23
+ # @return [Fixnum]
24
+ attribute :price
25
+
26
+ # Whether this sticker can be used ("active")
27
+ # @return [String]
28
+ attribute :state
29
+
30
+ # The uri for the sticker
31
+ # @return [String]
32
+ attribute :path
33
+
34
+ # Sets the current user's stickers.
35
+ #
36
+ # @param [Fixnum] top The y-coordinate of the sticker
37
+ # @param [Fixnum] left The x-coordinate of the sticker
38
+ # @param [Fixnum] angle The degree at which the sticker is angled
39
+ # @return [true]
40
+ # @raise [Turntabler::Error] if the command fails
41
+ # @example
42
+ # sticker.place(126, 78, -23) # => true
43
+ def place(top, left, angle)
44
+ api('sticker.place', :placement => [:sticker_id => id, :top => top, :left => left, :angle => angle], :is_dj => client.user.dj?, :roomid => room.id, :section => room.section)
45
+ true
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,25 @@
1
+ require 'turntabler/resource'
2
+ require 'turntabler/sticker'
3
+
4
+ module Turntabler
5
+ # Represents a sticker that's been placed on a user's laptop
6
+ class StickerPlacement < Resource
7
+ # The sticker that's been placed
8
+ # @return [Turntabler::Sticker]
9
+ attribute :sticker, :sticker_id do |value|
10
+ Sticker.new(client, :_id => value)
11
+ end
12
+
13
+ # The y-coordinate for the sticker
14
+ # @return [Fixnum]
15
+ attribute :top
16
+
17
+ # The x-coordinate for the sticker
18
+ # @return [Fixnum]
19
+ attribute :left
20
+
21
+ # The degree at which the sticker is tilted
22
+ # @return [Fixnum]
23
+ attribute :angle
24
+ end
25
+ end
@@ -0,0 +1,274 @@
1
+ require 'turntabler/resource'
2
+ require 'turntabler/avatar'
3
+ require 'turntabler/message'
4
+ require 'turntabler/sticker_placement'
5
+
6
+ module Turntabler
7
+ # Represents an unauthorized user on Turntable
8
+ class User < Resource
9
+ # Allow the id to be set via the "userid" attribute
10
+ # @return [String]
11
+ attribute :id, :userid, :load => false
12
+
13
+ # The DJ name for this user
14
+ # @return [String]
15
+ attribute :name, :name, :username
16
+
17
+ # The name of the laptop the DJ uses
18
+ # @return [String]
19
+ attribute :laptop_name, :laptop
20
+
21
+ # The version # for the laptop
22
+ # @return [String]
23
+ attribute :laptop_version
24
+
25
+ # The total number of points accumulated all-time
26
+ # @return [Fixnum]
27
+ attribute :points
28
+
29
+ # The access control determining what is authorized
30
+ # @return [Fixnum]
31
+ attribute :acl
32
+
33
+ # The number of fans this user has
34
+ # @return [Fixnum]
35
+ attribute :fans_count, :fans
36
+
37
+ # The user's unique identifier on Facebook (only available if the user is
38
+ # connected to the authorized user through Facebook)
39
+ # @return [String]
40
+ attribute :facebook_url, :facebook
41
+
42
+ # The user's unique identifier on Twitter (only available if the user is
43
+ # connected to the authorized user through Twitter)
44
+ # @return [String]
45
+ attribute :twitter_id, :twitter, :twitterid_lower
46
+
47
+ # The user's personal website
48
+ # @return [String]
49
+ attribute :website
50
+
51
+ # A brief description about the user
52
+ # @return [String]
53
+ attribute :about
54
+
55
+ # The user's favorite artists
56
+ # @return [String]
57
+ attribute :top_artists, :topartists
58
+
59
+ # Whether on Turntable the user likes to hang out
60
+ # @return [String]
61
+ attribute :hangout
62
+
63
+ # The user's currently active avatar
64
+ # @return [Turntabler::Avatar]
65
+ attribute :avatar, :avatarid do |value|
66
+ Avatar.new(client, :_id => value)
67
+ end
68
+
69
+ # The placements of stickers on the user's laptop
70
+ # @return [Array<Turntabler::StickerPlacement>]
71
+ attribute :sticker_placements, :placements do |placements|
72
+ placements.map {|attrs| StickerPlacement.new(client, attrs)}
73
+ end
74
+
75
+ # Loads the attributes for this user. Attributes will automatically load
76
+ # when accessed, but this allows data to be forcefully loaded upfront.
77
+ #
78
+ # @return [true]
79
+ # @raise [Turntabler::Error] if the command fails
80
+ # @example
81
+ # user.load # => true
82
+ # user.laptop_name # => "chrome"
83
+ def load
84
+ data = api('user.get_profile', :userid => id)
85
+ self.attributes = data
86
+ super
87
+ end
88
+
89
+ # Gets the availability status for this user.
90
+ #
91
+ # @return [String] "available" / "unavailable"
92
+ # @raise [Turntabler::Error] if the command fails
93
+ # @example
94
+ # user.presence # => "available"
95
+ def presence
96
+ data = api('presence.get', :uid => id)
97
+ data['presence']['status']
98
+ end
99
+
100
+ # Gets the stickers that are currently placed on the user.
101
+ #
102
+ # @param [Boolean] reload Whether to forcefully reload the user's list of sticker placements
103
+ # @return [Array<Turntabler::StickerPlacement>]
104
+ # @raise [Turntabler::Error] if the command fails
105
+ # @example
106
+ # user.sticker_placements # => [#<Turntabler::StickerPlacement ...>, ...]
107
+ def sticker_placements(reload = false)
108
+ self.attributes = api('sticker.get_placements', :userid => id) if reload || !@sticker_placements
109
+ @sticker_placements
110
+ end
111
+
112
+ # Marks the current user as a fan of this user.
113
+ #
114
+ # @return [true]
115
+ # @raise [Turntabler::Error] if the command fails
116
+ # @example
117
+ # user.become_fan # => true
118
+ def become_fan
119
+ api('user.become_fan', :djid => id)
120
+ true
121
+ end
122
+
123
+ # Marks the current user no longer as a fan of this user.
124
+ #
125
+ # @return [true]
126
+ # @raise [Turntabler::Error] if the command fails
127
+ # @example
128
+ # user.unfan # => true
129
+ def unfan
130
+ api('user.remove_fan', :djid => id)
131
+ true
132
+ end
133
+
134
+ # Sends a private message to this user.
135
+ #
136
+ # @return [true]
137
+ # @raise [Turntabler::Error] if the command fails
138
+ # @example
139
+ # user.say("Hey what's up?") # => true
140
+ def say(content)
141
+ api('pm.send', :receiverid => id, :text => content)
142
+ true
143
+ end
144
+
145
+ # Gets the private conversation history with this user.
146
+ #
147
+ # @return [Array<Turntabler::Message>]
148
+ # @raise [Turntabler::Error] if the command fails
149
+ # @example
150
+ # user.messages # => [#<Turntabler::Message ...>, ...]
151
+ def messages
152
+ data = api('pm.history', :receiverid => id)
153
+ data['history'].map {|attrs| Message.new(client, attrs)}
154
+ end
155
+
156
+ # Is the user currently a listener in the room?
157
+ #
158
+ # @return [Boolean] +true+ if the user is a listener, otherwise +false+
159
+ # @example
160
+ # user.listener? # => false
161
+ def listener?
162
+ !room.listener(id).nil?
163
+ end
164
+
165
+ # Is the user currently DJing in the room?
166
+ #
167
+ # @return [Boolean] +true+ if the user is a dj, otherwise +false+
168
+ # @example
169
+ # user.dj? # => false
170
+ def dj?
171
+ !room.dj(id).nil?
172
+ end
173
+
174
+ # Stops the user from DJing.
175
+ #
176
+ # @return [true]
177
+ # @raise [Turntabler::Error] if the command fails
178
+ # @example
179
+ # user.remove_as_dj # => true
180
+ def remove_as_dj
181
+ api('room.rem_dj', :roomid => room.id, :section => room.section, :djid => id)
182
+ true
183
+ end
184
+
185
+ # Is the user currently a moderator for the room?
186
+ #
187
+ # @return [Boolean] +true+ if the user is a moderator, otherwise +false+
188
+ # @example
189
+ # user.moderator? # => false
190
+ def moderator?
191
+ !room.moderator(id).nil?
192
+ end
193
+
194
+ # Adds the user as a moderator in the current room.
195
+ #
196
+ # user.add_as_moderator # => true
197
+ def add_as_moderator
198
+ api('room.add_moderator', :roomid => room.id, :section => room.section, :target_userid => id)
199
+ true
200
+ end
201
+
202
+ # Removes the user from being a moderator in the current room.
203
+ #
204
+ # @return [true]
205
+ # @raise [Turntabler::Error] if the command fails
206
+ # @example
207
+ # user.remove_as_moderator # => true
208
+ def remove_as_moderator
209
+ api('room.rem_moderator', :roomid => room.id, :section => room.section, :target_userid => id)
210
+ true
211
+ end
212
+
213
+ # Gets the location of the user.
214
+ #
215
+ # @note This will make the current user a fan of this user
216
+ # @param [Boolean] all_info Whether full detailed information should be provided about the room and user
217
+ # @return [Array<Turntabler::Room>]
218
+ # @raise [Turntabler::Error] if the command fails
219
+ # @example
220
+ # user.stalk # => #<Turntabler::User ...>
221
+ def stalk(all_info = false)
222
+ become_fan unless client.user.fan_of.include?(self)
223
+ client.rooms.with_friends.detect do |room|
224
+ room.listener(id)
225
+ end
226
+ end
227
+
228
+ # Blocks this user from being able to send private messages.
229
+ #
230
+ # @return [true]
231
+ # @raise [Turntabler::Error] if the command fails
232
+ # @example
233
+ # user.block # => true
234
+ def block
235
+ api('block.add', :blockedid => id)
236
+ true
237
+ end
238
+
239
+ # Unblocks this user from being able to send private messages.
240
+ #
241
+ # @return [true]
242
+ # @raise [Turntabler::Error] if the command fails
243
+ # @example
244
+ # user.unblock # => true
245
+ def unblock
246
+ api('block.remove', :blockedid => id)
247
+ true
248
+ end
249
+
250
+ # Boots the user for the specified reason.
251
+ #
252
+ # @param [String] reason The reason why the user is being booted
253
+ # @return [true]
254
+ # @raise [Turntabler::Error] if the command fails
255
+ # @example
256
+ # user.boot('Broke rules') # => true
257
+ def boot(reason = '')
258
+ api('room.boot_user', :roomid => room.id, :section => room.section, :target_userid => id, :reason => reason)
259
+ true
260
+ end
261
+
262
+ # Reports abuse by a user.
263
+ #
264
+ # @param [String] reason The reason the user is being reported
265
+ # @return [true]
266
+ # @raise [Turntabler::Error] if the command fails
267
+ # @example
268
+ # user.report('Verbal abuse ...') # => true
269
+ def report(reason = '')
270
+ api('room.report_user', :roomid => room.id, :section => room.section, :reported => id, :reason => reason)
271
+ true
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,9 @@
1
+ module Turntabler
2
+ # The current version of the library
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ PATCH = 1
7
+ STRING = [MAJOR, MINOR, PATCH].join(".")
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ require 'turntabler/resource'
2
+ require 'turntabler/user'
3
+
4
+ module Turntabler
5
+ # Represents a vote that was made within a room
6
+ class Vote < Resource
7
+ # The user who cast the vote
8
+ # @return [Turntabler::User]
9
+ attribute :user, :userid do |value|
10
+ room? ? room.build_user(:_id => value) : User.new(client, :_id => value)
11
+ end
12
+
13
+ # Whether the user voted +:up+ or +:down+
14
+ # @return [Symbol]
15
+ attribute :direction do |value|
16
+ value.to_sym
17
+ end
18
+ end
19
+ end