google_music_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1756fd782e96633e9721d277a68c08355969d4d3
4
+ data.tar.gz: a26b57d2536ee5db3af5b29318d043e34251c0c7
5
+ SHA512:
6
+ metadata.gz: 02090b2236c989a3d4c1a45b003ebfefcd262ac32dc311bbdacd4527844ff1a716861c7e6e703880fe970a645aeb3f92f9cf2512a409a070e16b0fc2f93cf2c3
7
+ data.tar.gz: 3d54ab3c29b10a970a7c32a27e471693f667b3f643bc31acf8ed7c781999e2322122ae1d9ae0b71e537b5c8b1f1aa97bfbf1825e21416305250056bde031a782
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
@@ -0,0 +1,4 @@
1
+ #Changelog
2
+
3
+ ##0.1.0
4
+ Initial version released
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at alex@poloniculmov.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in google_music_api.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 TODO: Write your name
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.
@@ -0,0 +1,51 @@
1
+ # GoogleMusicApi
2
+
3
+ This is a port of Simon Webber's cool [`gmusicapi`](https://github.com/simon-weber/gmusicapi) Python library. It currently
4
+ only implements the `MobileClient` methods. I don't have any plans to add support for `Webclient` or `MusicManager` at
5
+ this moment, but I would welcome anybody who wants to add it.
6
+
7
+
8
+ ## Notes
9
+ Google Two-Factor Authentication is not supported at the moment, you will have to create a app
10
+ password for using this.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'google_music_api'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install google_music_api
27
+
28
+ ## Usage
29
+
30
+ ```ruby
31
+ client = GoogleMusicApi::Mobileclient.new
32
+
33
+ client.login(email, password)
34
+
35
+ client.search('Radiohead')
36
+ ```
37
+ ## Development
38
+
39
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
40
+
41
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
42
+
43
+ ## Contributing
44
+
45
+ Bug reports and pull requests are welcome on GitHub at https://github.com/poloniculmov/google_music_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
46
+
47
+
48
+ ## License
49
+
50
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
51
+
@@ -0,0 +1,21 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ task :console do
9
+ require 'pry'
10
+ require 'google_music_api'
11
+
12
+ def reload!
13
+ # Change 'gem_name' here too:
14
+ files = $LOADED_FEATURES.select { |feat| feat =~ /\/google_music_api\// }
15
+ files.each { |file| load file }
16
+ end
17
+
18
+ ARGV.clear
19
+ Pry.start
20
+ end
21
+
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "google_music_api"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'pry'
14
+ require 'google_music_api'
15
+
16
+ def reload!
17
+ # Change 'gem_name' here too:
18
+ files = $LOADED_FEATURES.select { |feat| feat =~ /\/google_music_api\// }
19
+ files.each { |file| load file }
20
+ end
21
+
22
+ ARGV.clear
23
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'google_music_api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "google_music_api"
8
+ spec.version = GoogleMusicApi::VERSION
9
+ spec.authors = ["poloniculmov"]
10
+ spec.email = ["alex@poloniculmov.com"]
11
+
12
+ spec.summary = %q{Unofficial Google Music API client}
13
+ spec.homepage = "https://github.com/poloniculmov/google_music_api"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.12"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency "webmock"
26
+
27
+ spec.add_dependency 'gpsoauth', '~> 0.2.0'
28
+ spec.add_dependency 'httparty'
29
+ end
@@ -0,0 +1,7 @@
1
+ require 'google_music_api/version'
2
+ require 'google_music_api/mobile_client'
3
+ require 'google_music_api/errors'
4
+
5
+ module GoogleMusicApi
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,30 @@
1
+ module GoogleMusicApi
2
+ module Album
3
+
4
+ #Gets an album's details
5
+ # @param [string] album_id
6
+ # @param [string] include_tracks
7
+ # @return [Hash] describing an album and tracks
8
+ def get_album_info(album_id, include_tracks = true)
9
+ url = 'fetchalbum'
10
+
11
+ options = {
12
+ query: {
13
+ nid: album_id,
14
+ 'include-tracks': include_tracks
15
+ }
16
+ }
17
+
18
+ make_get_request(url, options)
19
+ end
20
+
21
+ #Searches albums
22
+ # @param [string] query
23
+ # @param [integer] max_results
24
+ # @return [Hash] describing albums
25
+ def search_albums(query, max_results=50)
26
+ search(query, '3', max_results)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module GoogleMusicApi
2
+ module Artist
3
+
4
+ # Gets an artists details
5
+ # @param [String] artist_id
6
+ # @param [boolean] include_albums
7
+ # @param [integer] max_top_tracks
8
+ # @param [integer] max_related_artists
9
+ def get_artist_info(artist_id, include_albums = true, max_top_tracks = 5, max_related_artists = 5)
10
+ url = 'fetchartist'
11
+
12
+ options = {
13
+ query: {
14
+ nid: artist_id,
15
+ 'include-albums': include_albums,
16
+ 'num-top-tracks': max_top_tracks,
17
+ 'num-related-artists': max_related_artists
18
+ }
19
+ }
20
+
21
+ make_get_request(url, options)
22
+ end
23
+
24
+ #Searches albums
25
+ # @param [string] query
26
+ # @param [integer] max_results
27
+ # @return [Hash] describing artists
28
+ def search_artists(query, max_results=50)
29
+ search(query, '2', max_results)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module GoogleMusicApi
2
+ class ApiError < StandardError
3
+
4
+ end
5
+
6
+ class AuthenticationError < ApiError
7
+
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module GoogleMusicApi
2
+ #Keeps all the genre-related methods
3
+ module Genre
4
+ #Returns all genres or all subgenres if parent_id is passed
5
+ # @param [String] parent_id
6
+ # @return [Array] of hashes that describe a genre
7
+ # @example Get all the subgenres of Jazz
8
+ # mobile_client.get_genres('JAZZ')
9
+ def get_genres(parent_id = nil)
10
+ url = 'explore/genres'
11
+
12
+ options = {}
13
+ options[:query] = {'parent-genre': parent_id} if parent_id
14
+
15
+ make_get_request(url, options)['genres']
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module GoogleMusicApi
2
+ module Http
3
+
4
+ private
5
+
6
+ def make_get_request(url, options = {})
7
+ url ="#{self.class::SERVICE_ENDPOINT}#{url}"
8
+ options[:headers] = {'Authorization': 'GoogleLogin auth='+authorization_token}
9
+ HTTParty.get(url, options).parsed_response
10
+ end
11
+
12
+ def make_post_request(url, options = {})
13
+ url ="#{self.class::SERVICE_ENDPOINT}#{url}"
14
+ options[:headers] = {'Authorization': 'GoogleLogin auth='+authorization_token, 'Content-Type': 'application/json'}
15
+ HTTParty.post(url, options).parsed_response
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ module GoogleMusicApi
2
+ #Holds all the library related methods
3
+ module Library
4
+
5
+ #Gets all tracks in the library#
6
+ # @return [Array] of hashes describing tracks
7
+ def get_all_tracks
8
+ url = 'trackfeed'
9
+
10
+ make_post_request(url)['data']['items']
11
+ end
12
+
13
+ #Gets the promoted songs
14
+ # @return [Array] of hashes describing tracks
15
+ def get_promoted_songs
16
+ url = 'ephemeral/top'
17
+
18
+ make_post_request(url)['data']['items']
19
+ end
20
+
21
+ # Gets all listen now items
22
+ # return [Array] of hashes, each hash can describe a different kind of item Station/Track/Album
23
+ def get_listen_now_items
24
+ url = 'listennow/getlistennowitems'
25
+ options = {'alt': 'json'}
26
+
27
+ make_get_request(url)['listennow_items']
28
+ end
29
+
30
+ def add_tracks_to_library(song_ids = [])
31
+ #TODO: Implement after adding Hashie support as this needs an extra call
32
+ url = 'trackbatch'
33
+
34
+ throw NotImplementedError.new
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,100 @@
1
+ require 'gpsoauth'
2
+ require 'google_music_api/http'
3
+ require 'google_music_api/genre'
4
+ require 'google_music_api/playlist'
5
+ require 'google_music_api/library'
6
+ require 'google_music_api/station'
7
+ require 'google_music_api/album'
8
+ require 'google_music_api/artist'
9
+ require 'google_music_api/track'
10
+
11
+ module GoogleMusicApi
12
+ class MobileClient
13
+
14
+ SERVICE = 'sj'
15
+ APP = 'com.google.android.music'
16
+ CLIENT_SIGNATURE = '38918a453d07199354f8b19af05ec6562ced5788'
17
+
18
+ SERVICE_ENDPOINT = 'https://mclients.googleapis.com/sj/v2.4/'
19
+
20
+ include Http
21
+ include Genre
22
+ include Playlist
23
+ include Library
24
+ include Station
25
+ include Album
26
+ include Artist
27
+ include Track
28
+
29
+ #Pass an authorization token and you won't have to login
30
+ # @param [string] authorization_token
31
+ def initialize(authorization_token = nil)
32
+ @authorization_token = authorization_token
33
+ end
34
+
35
+
36
+ # Logs in to Google using OAuth and obtains an authorization token
37
+ #
38
+ # @param email [String] your email
39
+ # @param [String] password you password
40
+ # @param [String] android_id 16 hex digits, eg '1234567890abcdef'
41
+ # @param [String] device_country the country code of the device you're impersonating, default = 'us'
42
+ # @param [String] operator_country the country code of the device's mobile operator, default = 'us'
43
+ #
44
+ # @raise [AuthenticationError] if authentication fails
45
+ # @return true if success
46
+ def login(email, password, android_id, device_country='us', operator_country='us')
47
+ g = Gpsoauth::Client.new(android_id, 'ac2dm', device_country, operator_country)
48
+
49
+ response = g.master_login(email, password)
50
+ oauth_response = g.oauth(email, response['Token'], SERVICE, APP, CLIENT_SIGNATURE)
51
+
52
+ raise AuthenticationError.new('Invalid username/password') unless oauth_response.key?('Auth')
53
+ @authorization_token = oauth_response['Auth']
54
+ true
55
+ end
56
+
57
+ # Checks if there's an authorization token present
58
+ # @return [boolean]
59
+ def authenticated?
60
+ !!@authorization_token
61
+ end
62
+
63
+ # Checks whether the user is subscribed or not
64
+ # @return [boolean]
65
+ def subscribed?
66
+ url = 'config'
67
+ options = {query: {dv: 0}}
68
+
69
+ subscribed = make_get_request(url, options)['data']['entries'].find do |item|
70
+ item['key'] == 'isNautilusUser' && item['value'] == 'true'
71
+ end
72
+
73
+ !subscribed.nil?
74
+ end
75
+
76
+ #Generic search
77
+ # 1: Song, 2: Artist, 3: Album, 4: Playlist, 6: Station, 7: Situation, 8: Video
78
+ # @param [string] query
79
+ # @param [string] ct Used to restrict search to specific items, comma-separated list of item type ids.
80
+ # @param [integer] max_results
81
+ def search(query, ct = '1,2,3,4,6,7,8', max_results = 50)
82
+ url = 'query'
83
+
84
+ options = {
85
+ query: {
86
+ 'ct': ct,
87
+ 'q': query,
88
+ 'max-results': max_results
89
+ }
90
+ }
91
+
92
+ make_get_request(url, options)['entries']
93
+ end
94
+
95
+ def authorization_token
96
+ @authorization_token
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,205 @@
1
+ module GoogleMusicApi
2
+ #Holds all the playlist related methods
3
+ module Playlist
4
+
5
+ #Gets all the playlists that user has
6
+ #@return [Array] of Hashes that describe a playlist
7
+ def get_all_playlists
8
+ url = 'playlistfeed'
9
+
10
+ make_post_request(url).fetch('data', {'items' => []})['items']
11
+ end
12
+
13
+ #Gets all the tracks in the user's own playlists. Doesn't include subscribed playlists. If you want to get the tracks of a single playlist, use
14
+ # {get_shared_playlists_entries}
15
+ #@return [Array] of hashes describing playlist entries. Each Playlist entry will have the playlist's id and the track description
16
+ def get_own_playlists_entries
17
+ url = 'plentryfeed'
18
+
19
+ make_post_request(url).fetch('data', {'items' => []})['items']
20
+ end
21
+
22
+
23
+ #Gets all the playlist entries of a playlist that user is subscribed to
24
+ #@return [Array] of describing playlist entries
25
+ # @param [String] share_token - this is not the playlist's id, but the share_token
26
+ # @example get_shared_playlists_entries 'AMaBXykmxW9ZMK3C7WvoLKDw8AQB00UNiHXEwWwFRZ8lGc9yfb-SFI6nmj6t-IZKE5AvKFlVSzmU2wmQ2j0e3RqCJe4ytpBAMQ=='
27
+ def get_shared_playlists_entries(share_token)
28
+ url = 'plentries/shared'
29
+
30
+ options = {body: {entries: [{
31
+ shareToken: share_token
32
+ }]
33
+ }.to_json
34
+ }
35
+
36
+ make_post_request(url, options)
37
+ end
38
+
39
+
40
+ #Creates a playlist
41
+ # @param [String] name
42
+ # @param [String] description, default = ''
43
+ # @param [boolean] public, default = false
44
+ # @return [Hash] with the API response, status is in the 'response_code' key
45
+ def create_playlist(name, description = '', public = false)
46
+ create_playlists [{name: name, description: description, public: public}][0]
47
+ end
48
+
49
+ #Batch creates playlists
50
+ # @param [Array] playlist_descriptions, consists of one or more hashes with the keys :name, :description and :public
51
+ # @return [Array] of hashes with the result of each creation operation
52
+ def create_playlists(playlist_descriptions = [])
53
+ url = 'playlistbatch'
54
+
55
+ creates = playlist_descriptions.map do |pd|
56
+ {
57
+ create: {
58
+ creationTimestamp: '-1',
59
+ deleted: false,
60
+ lastModifiedTimestamp: '0',
61
+ name: pd[:name],
62
+ description: pd.fetch(:description, ''),
63
+ type: 'USER_GENERATED',
64
+ shareState: (pd.fetch(:public, false) ? 'PUBLIC' : 'PRIVATE')
65
+ }
66
+ }
67
+ end
68
+
69
+ options = {
70
+ body: {mutations: creates}.to_json
71
+ }
72
+ make_post_request(url, options).fetch('mutate_response')
73
+ end
74
+
75
+ # Updates a single playlist
76
+ # @param [string] id, the actual id, not the share token
77
+ # @param [string] new_name
78
+ # @param [string] new_description
79
+ # @param [boolean] new_public
80
+ # @return [Hash] with the API response, status is in the 'response_code' key
81
+ def update_playlist(id, new_name = nil, new_description = nil, new_public = nil)
82
+ update_playlists [{id: id, name: new_name, description: new_description, public: new_public}][0]
83
+ end
84
+
85
+ # Batch updates one or more playlists
86
+ # @param [Array] playlist_descriptions, consists of one or more hashes with the keys id:, :name, :description and :public
87
+ # @return [Array] of hashes with the result of each creation operation
88
+ def update_playlists(playlist_descriptions)
89
+ url = 'playlistbatch'
90
+
91
+ updates = playlist_descriptions.map do |pd|
92
+ {
93
+ update: {
94
+ id: pd[:id],
95
+ name: pd[:name],
96
+ description: pd[:description],
97
+ shareState: (pd[:public] ? 'PUBLIC' : 'PRIVATE')
98
+ }
99
+ }
100
+ end
101
+
102
+ options = {
103
+ body: {mutations: updates}.to_json
104
+ }
105
+
106
+ make_post_request(url, options).fetch('mutate_response')
107
+ end
108
+
109
+ #Deletes a playlist
110
+ # @param [string] id
111
+ # @return [Hash] with the API response, status is in the 'response_code' key
112
+ def delete_playlist(id)
113
+ delete_playlists([id])[0]
114
+ end
115
+
116
+ # Batch deletes one or more playlists
117
+ # @return [Array] of hashes with the result of each creation operation
118
+ # @param [Array] ids
119
+ def delete_playlists(ids = [])
120
+ url = 'playlistbatch'
121
+
122
+ deletes = ids.map do |pd|
123
+ {
124
+ delete: pd
125
+ }
126
+ end
127
+
128
+ options = {
129
+ body: {mutations: deletes}.to_json
130
+ }
131
+
132
+ make_post_request(url, options).fetch('mutate_response')
133
+ end
134
+
135
+ # Adds tracks to a playlist
136
+ # @param [string] playlist_id
137
+ # @param [array] track_ids
138
+ def add_tracks_to_playlist(playlist_id, track_ids)
139
+ url = 'plentriesbatch'
140
+ options = {}
141
+
142
+
143
+ prev_id, cur_id, next_id = nil, SecureRandom.uuid, SecureRandom.uuid
144
+
145
+ mutations = []
146
+ track_ids.each_with_index do |value, index|
147
+ m_details = {
148
+ clientId: cur_id,
149
+ creationTimestamp: '-1',
150
+ deleted: false,
151
+ lastModifiedTimestamp: '0',
152
+ playlistId: playlist_id,
153
+ source: 1,
154
+ trackId: value,
155
+ }
156
+
157
+ m_details[:source] = 2 if value[0] == 'T'
158
+
159
+ m_details[:precedingEntryId] = prev_id if index > 0
160
+
161
+ m_details[:followingEntryId] = next_id if index < value.length - 1
162
+
163
+ mutations << {create: m_details}
164
+
165
+ prev_id, cur_id, next_id = cur_id, next_id, SecureRandom.uuid
166
+ end
167
+
168
+
169
+ options[:body] = {mutations: mutations}.to_json
170
+
171
+ make_post_request(url, options)
172
+ end
173
+
174
+
175
+ #Batch removes track from playlists. You can send playlist entries of multiple playlists
176
+ # @param [Array] playlist_entry_ids These are playlist entries, not track ids
177
+ def remove_tracks_from_playlist(playlist_entry_ids)
178
+ url = 'plentriesbatch'
179
+
180
+ mutations = playlist_entry_ids.map { |id| {delete: id}}
181
+ options = {
182
+ body: {
183
+ mutations: mutations
184
+ }.to_json
185
+ }
186
+
187
+ make_post_request url, options
188
+ end
189
+
190
+ def reorder_playlist_entry
191
+ throw NotImplementedError.new
192
+ end
193
+
194
+ protected
195
+
196
+ def add_track_type(track_id)
197
+ if track_id[0] == 'T'
198
+ {'id': track_id, 'type': 1}
199
+ else
200
+ {'id': track_id, 'type': 0}
201
+ end
202
+ end
203
+
204
+ end
205
+ end
@@ -0,0 +1,65 @@
1
+ module GoogleMusicApi
2
+ module Station
3
+
4
+ #Gets the current listen now situations and their associated stations
5
+ # @return [Array] of situations and their respected stations
6
+ def get_listen_now_situations
7
+ url = 'listennow/situations'
8
+ options = {query: {'alt': 'json', 'tier': 'aa', 'hl': 'en_US'}}
9
+
10
+ body = {'requestSignals': {'timeZoneOffsetSecs': Time.now.gmt_offset}}.to_json
11
+
12
+ options[:body] = body
13
+ make_post_request(url, options)['situations']
14
+ end
15
+
16
+ # Gets all radio stations
17
+ # It seems to be tied to what the user
18
+ # @return [Array] of radios stations
19
+ def get_all_stations
20
+ url = 'radio/station'
21
+ options = {query: {'alt': 'json', 'tier': 'aa', 'hl': 'en_US'}}
22
+
23
+ make_post_request(url, options)
24
+ end
25
+
26
+ # Gets a station's tracks
27
+ # @param [string] station_id
28
+ # @param [integer] number_of_tracks
29
+ # @param [Array] recently_played track ids
30
+ # @return [Array] of tracks
31
+ def get_station_tracks(station_id, number_of_tracks = 25, recently_played = [])
32
+ url = 'radio/stationfeed'
33
+ options = {query: {'alt': 'json', 'include-tracks': 'true', 'tier': 'aa', 'hl': 'en_US'}}
34
+
35
+ options[:body] = {'contentFilter': 1,
36
+ 'stations': [
37
+ {
38
+ 'numEntries': number_of_tracks,
39
+ 'radioId': station_id,
40
+ 'recentlyPlayed': recently_played.map { |rec| add_track_type rec }
41
+ }
42
+ ]}.to_json
43
+
44
+ make_post_request(url, options)['data']['stations']
45
+ end
46
+
47
+ #Gets I'm feeling lucky station tracks
48
+ # @param [integer] number_of_tracks
49
+ # @param [Array] recently_played track ids
50
+ # @return [Array] of tracks
51
+ def get_im_feeling_lucky_tracks(number_of_tracks = 25, recently_played = [])
52
+ get_station_tracks 'IFL', number_of_tracks, recently_played
53
+ end
54
+
55
+ def create_station
56
+ throw NotImplementedError.new
57
+ end
58
+
59
+ def delete_station
60
+ throw NotImplementedError.new
61
+ end
62
+
63
+
64
+ end
65
+ end
@@ -0,0 +1,60 @@
1
+ module GoogleMusicApi
2
+ module Track
3
+ #Gets details about a track
4
+ # @param [string] track_id
5
+ # @return [hash] describing the track
6
+ def get_track_info(track_id)
7
+ url = 'fetchtrack'
8
+
9
+ options = {
10
+ query: {
11
+ nid: track_id
12
+ }
13
+ }
14
+
15
+ make_get_request url, options
16
+ end
17
+
18
+ #Increases a tracks play count
19
+ # @param [string] song_id
20
+ # @param [integer] number_of_plays
21
+ # @param [Time] play_time
22
+ def increase_track_play_count(song_id, number_of_plays = 1, play_time = Time.now)
23
+ url = 'trackstats'
24
+
25
+ options = {
26
+ query: {
27
+ alt: 'json'
28
+ }
29
+ }
30
+
31
+ play_timestamp = (play_time.to_f * 1000).to_i
32
+ event = {
33
+ context_type: 1,
34
+ event_timestamp_micros: play_timestamp,
35
+ event_type: 2
36
+ }
37
+
38
+ options[:body] = {
39
+ track_stats: [{
40
+ id: song_id,
41
+ incremental_plays: number_of_plays,
42
+ last_play_time_millis: play_timestamp,
43
+ type: song_id[0] == 'T' ? 2 : 1,
44
+ track_events: [event] * number_of_plays
45
+ }]
46
+ }.to_json
47
+
48
+ make_post_request url, options
49
+ end
50
+
51
+ #Searches tracks
52
+ # @param [string] query
53
+ # @param [integer] max_results
54
+ # @return [Hash] describing tracks
55
+ def search_tracks(query, max_results=50)
56
+ search(query, '1', max_results)
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module GoogleMusicApi
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google_music_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - poloniculmov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: gpsoauth
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: httparty
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - alex@poloniculmov.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - CHANGELOG.md
122
+ - CODE_OF_CONDUCT.md
123
+ - Gemfile
124
+ - LICENSE.txt
125
+ - README.md
126
+ - Rakefile
127
+ - bin/console
128
+ - bin/setup
129
+ - google_music_api.gemspec
130
+ - lib/google_music_api.rb
131
+ - lib/google_music_api/album.rb
132
+ - lib/google_music_api/artist.rb
133
+ - lib/google_music_api/errors.rb
134
+ - lib/google_music_api/genre.rb
135
+ - lib/google_music_api/http.rb
136
+ - lib/google_music_api/library.rb
137
+ - lib/google_music_api/mobile_client.rb
138
+ - lib/google_music_api/playlist.rb
139
+ - lib/google_music_api/station.rb
140
+ - lib/google_music_api/track.rb
141
+ - lib/google_music_api/version.rb
142
+ homepage: https://github.com/poloniculmov/google_music_api
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.5.1
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Unofficial Google Music API client
166
+ test_files: []