turntabler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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