google_music_api 0.1.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.
@@ -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: []