poke-go-api 0.0.8 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f8a4ee180cf34ffc94cebb3b8bb6528b17d19382
4
- data.tar.gz: b4fd14e947d0427c1ef50784525d3086b6e8fa8e
3
+ metadata.gz: 7d4e06820ff24205fd6343be9abac2400d08b2e3
4
+ data.tar.gz: 1f3294582db49573f3805226e6515d6b42c94d37
5
5
  SHA512:
6
- metadata.gz: a9a59b5cc9518cdf34390f1128771490bb382e67d57ae55c69235f4859043f2bb8074195375164a9a628e80b29b36e8ebc26dc1988bd3f148e4873c0a14959bd
7
- data.tar.gz: ef2d410e34aafa7fb3d1b5f3f85b83e82b22bdf5823177f218b7c84d3f1e5b773f389e4be747c5779944768408a2575acfcbcfad873b5a75795fa09633f6071c
6
+ metadata.gz: 73b3a3e1c09ffdaca9bfbb14d5f08023dafc78ee4dc0b5339b673ad99e0d91ad401e0957fdd0701846747b25c98c01b78dd7d790d5634c0faf39c3eac6f0125c
7
+ data.tar.gz: d8b0b6f96f603393cd721172ca9308f4de3198605910c8d3b54b64ff892f6dfcf901811aa433d1f6790ff43667afe1537a3d45ce6ff59b90d322d808a3f47d12
data/lib/poke-api.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Load Poke-API related classes
1
+ # Load main Poke::API related classes
2
2
  require 'poke-api/logging'
3
3
  require 'poke-api/client'
4
4
  require 'poke-api/helpers'
@@ -8,7 +8,15 @@ require 'poke-api/errors'
8
8
  require 'poke-api/version'
9
9
  require 'poke-api/auth/ptc'
10
10
  require 'poke-api/auth/google'
11
+ require 'poke-api/auth/ticket'
12
+
13
+ # Load Geometry libraries (native Ruby)
14
+ require 'poke-api/geometry/s2_base'
15
+ require 'poke-api/geometry/s2_cell_id'
16
+ require 'poke-api/geometry/s2_lat_lon'
17
+ require 'poke-api/geometry/s2_point'
11
18
 
12
19
  # Load Google Generated POGOProtos
20
+ require 'poke-api/POGOProtos/Signature'
13
21
  require 'poke-api/POGOProtos/Networking/Envelopes/RequestEnvelope'
14
22
  require 'poke-api/POGOProtos/Networking/Envelopes/ResponseEnvelope'
@@ -0,0 +1,94 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: Signature.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "Signature" do
8
+ optional :timestamp_since_start, :uint64, 2
9
+ repeated :location_fix, :message, 4, "Signature.LocationFix"
10
+ optional :gps_info, :message, 5, "Signature.AndroidGpsInfo"
11
+ optional :sensor_info, :message, 7, "Signature.SensorInfo"
12
+ optional :device_info, :message, 8, "Signature.DeviceInfo"
13
+ optional :activity_status, :message, 9, "Signature.ActivityStatus"
14
+ optional :location_hash1, :uint32, 10
15
+ optional :location_hash2, :uint32, 20
16
+ optional :unk22, :bytes, 22
17
+ optional :timestamp, :uint64, 23
18
+ repeated :request_hash, :uint64, 24
19
+ end
20
+ add_message "Signature.LocationFix" do
21
+ optional :provider, :string, 1
22
+ optional :timestamp_since_start, :uint64, 2
23
+ optional :latitude, :float, 13
24
+ optional :longitude, :float, 14
25
+ optional :horizontal_accuracy, :float, 20
26
+ optional :altitude, :float, 21
27
+ optional :vertical_accuracy, :float, 22
28
+ optional :provider_status, :uint64, 26
29
+ optional :floor, :uint32, 27
30
+ optional :location_type, :uint64, 28
31
+ end
32
+ add_message "Signature.AndroidGpsInfo" do
33
+ optional :time_to_fix, :uint64, 1
34
+ repeated :satellites_prn, :int32, 2
35
+ repeated :snr, :float, 3
36
+ repeated :azimuth, :float, 4
37
+ repeated :elevation, :float, 5
38
+ repeated :has_almanac, :bool, 6
39
+ repeated :has_ephemeris, :bool, 7
40
+ repeated :used_in_fix, :bool, 8
41
+ end
42
+ add_message "Signature.SensorInfo" do
43
+ optional :timestamp_snapshot, :uint64, 1
44
+ optional :magnetometer_x, :double, 3
45
+ optional :magnetometer_y, :double, 4
46
+ optional :magnetometer_z, :double, 5
47
+ optional :angle_normalized_x, :double, 6
48
+ optional :angle_normalized_y, :double, 7
49
+ optional :angle_normalized_z, :double, 8
50
+ optional :accel_raw_x, :double, 10
51
+ optional :accel_raw_y, :double, 11
52
+ optional :accel_raw_z, :double, 12
53
+ optional :gyroscope_raw_x, :double, 13
54
+ optional :gyroscope_raw_y, :double, 14
55
+ optional :gyroscope_raw_z, :double, 15
56
+ optional :accel_normalized_x, :double, 16
57
+ optional :accel_normalized_y, :double, 17
58
+ optional :accel_normalized_z, :double, 18
59
+ optional :accelerometer_axes, :uint64, 19
60
+ end
61
+ add_message "Signature.DeviceInfo" do
62
+ optional :device_id, :string, 1
63
+ optional :android_board_name, :string, 2
64
+ optional :android_bootloader, :string, 3
65
+ optional :device_brand, :string, 4
66
+ optional :device_model, :string, 5
67
+ optional :device_model_identifier, :string, 6
68
+ optional :device_model_boot, :string, 7
69
+ optional :hardware_manufacturer, :string, 8
70
+ optional :hardware_model, :string, 9
71
+ optional :firmware_brand, :string, 10
72
+ optional :firmware_tags, :string, 12
73
+ optional :firmware_type, :string, 13
74
+ optional :firmware_fingerprint, :string, 14
75
+ end
76
+ add_message "Signature.ActivityStatus" do
77
+ optional :start_time_ms, :uint64, 1
78
+ optional :unknown_status, :bool, 2
79
+ optional :walking, :bool, 3
80
+ optional :running, :bool, 4
81
+ optional :stationary, :bool, 5
82
+ optional :automotive, :bool, 6
83
+ optional :tilting, :bool, 7
84
+ optional :cycling, :bool, 8
85
+ optional :status, :bytes, 9
86
+ end
87
+ end
88
+
89
+ Signature = Google::Protobuf::DescriptorPool.generated_pool.lookup("Signature").msgclass
90
+ Signature::LocationFix = Google::Protobuf::DescriptorPool.generated_pool.lookup("Signature.LocationFix").msgclass
91
+ Signature::AndroidGpsInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("Signature.AndroidGpsInfo").msgclass
92
+ Signature::SensorInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("Signature.SensorInfo").msgclass
93
+ Signature::DeviceInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("Signature.DeviceInfo").msgclass
94
+ Signature::ActivityStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("Signature.ActivityStatus").msgclass
@@ -0,0 +1,100 @@
1
+ syntax = "proto3";
2
+
3
+ message Signature {
4
+
5
+ message LocationFix {
6
+ string provider = 1; // "network", "gps", "fused", possibly others
7
+ uint64 timestamp_since_start = 2; // in ms
8
+ float latitude = 13;
9
+ float longitude = 14;
10
+
11
+ // ??? shows up in struct, dunno where these go
12
+ // float device_speed;
13
+ // float device_course;
14
+ float horizontal_accuracy = 20; // iOS only? (range seems to be -1 to +1)
15
+ float altitude = 21;
16
+ float vertical_accuracy = 22; // iOS only? (range seems to be ~10-12)
17
+ uint64 provider_status = 26; // Usually 3 (possibly GPS status: 1 = no fix, 2 = acquiring/inaccurate, 3 = fix acquired)
18
+ // On iOS there are some LocationFixes with unk26=1 and everything else empty
19
+ uint32 floor = 27; // No idea what this is, seems to be optional
20
+ uint64 location_type = 28; // Always 1 (if there is data at all)
21
+ }
22
+
23
+ // don't really care about this since we're not using it
24
+ message AndroidGpsInfo {
25
+ uint64 time_to_fix = 1;
26
+ repeated int32 satellites_prn = 2;
27
+ repeated float snr = 3;
28
+ repeated float azimuth = 4;
29
+ repeated float elevation = 5;
30
+ repeated bool has_almanac = 6;
31
+ repeated bool has_ephemeris = 7;
32
+ repeated bool used_in_fix = 8;
33
+ }
34
+
35
+ message SensorInfo {
36
+ uint64 timestamp_snapshot = 1; // in ms
37
+ double magnetometer_x = 3;
38
+ double magnetometer_y = 4;
39
+ double magnetometer_z = 5;
40
+ double angle_normalized_x = 6;
41
+ double angle_normalized_y = 7;
42
+ double angle_normalized_z = 8;
43
+ double accel_raw_x = 10;
44
+ double accel_raw_y = 11;
45
+ double accel_raw_z = 12;
46
+ double gyroscope_raw_x = 13;
47
+ double gyroscope_raw_y = 14;
48
+ double gyroscope_raw_z = 15;
49
+ double accel_normalized_x = 16;
50
+ double accel_normalized_y = 17;
51
+ double accel_normalized_z = 18;
52
+ uint64 accelerometer_axes = 19; // Always 3
53
+ }
54
+
55
+ message DeviceInfo {
56
+ string device_id = 1; // Hex string
57
+ string android_board_name = 2;
58
+ string android_bootloader = 3;
59
+ string device_brand = 4; // On Android: product.brand
60
+ string device_model = 5; // On Android: product.device
61
+ string device_model_identifier = 6; // Android only, build.display.id
62
+ string device_model_boot = 7; // On Android: boot.hardware
63
+ string hardware_manufacturer = 8; // On Android: product.manufacturer
64
+ string hardware_model = 9; // On Android: product.model
65
+ string firmware_brand = 10; // On Android: product.name, on iOS: "iPhone OS"
66
+ string firmware_tags = 12; // Android only, build.tags
67
+ string firmware_type = 13; // On Android: build.type, on iOS instead: iOS version
68
+ string firmware_fingerprint = 14; // Android only, build.fingerprint
69
+ }
70
+
71
+ message ActivityStatus {
72
+ // all of these had 1 as their value
73
+ uint64 start_time_ms = 1;
74
+ bool unknown_status = 2;
75
+ bool walking = 3;
76
+ bool running = 4;
77
+ bool stationary = 5;
78
+ bool automotive = 6;
79
+ bool tilting = 7;
80
+ bool cycling = 8;
81
+ bytes status = 9;
82
+ }
83
+
84
+ uint64 timestamp_since_start = 2; // in ms
85
+ repeated LocationFix location_fix = 4;
86
+ AndroidGpsInfo gps_info = 5;
87
+ SensorInfo sensor_info = 7;
88
+ DeviceInfo device_info = 8;
89
+ ActivityStatus activity_status = 9;
90
+ uint32 location_hash1 = 10; // Location1 hashed based on the auth_token - xxHash32
91
+ uint32 location_hash2 = 20; // Location2 hashed based on the auth_token - xxHash32
92
+ bytes unk22 = 22; // possibly replay check. Generation unknown but pointed to by 0001B8614
93
+ uint64 timestamp = 23; // epoch timestamp in ms
94
+ repeated uint64 request_hash = 24; // hashes of each request message in a hashArray - xxhash64
95
+
96
+ // Addresses for the corresponding hash functions:
97
+ // xxHash32 00054D28
98
+ // xxhash64 000546C8 - Feeds into 00053D40
99
+
100
+ }
@@ -7,11 +7,11 @@ module Poke
7
7
  include Logging
8
8
  attr_reader :access_token, :provider
9
9
 
10
- GOOGLE_LOGIN_ANDROID_ID = '9774d56d682e549c'
10
+ GOOGLE_LOGIN_ANDROID_ID = '9774d56d682e549c'.freeze
11
11
  GOOGLE_LOGIN_SERVICE = 'audience:server:client_id:848232511240-7so421jotr' \
12
- '2609rmqakceuu1luuq0ptb.apps.googleusercontent.com'
13
- GOOGLE_LOGIN_APP = 'com.nianticlabs.pokemongo'
14
- GOOGLE_LOGIN_CLIENT_SIG = '321187995bc7cdc2b5fc91b11a96e2baa8602c62'
12
+ '2609rmqakceuu1luuq0ptb.apps.googleusercontent.com'.freeze
13
+ GOOGLE_LOGIN_APP = 'com.nianticlabs.pokemongo'.freeze
14
+ GOOGLE_LOGIN_CLIENT_SIG = '321187995bc7cdc2b5fc91b11a96e2baa8602c62'.freeze
15
15
 
16
16
  def initialize(username, password)
17
17
  @username = username
@@ -40,7 +40,7 @@ module Poke
40
40
  response = yield
41
41
 
42
42
  if response['Error'] == 'NeedsBrowser'
43
- raise Errors::GoogleTwoFactorAuthenticationFailure.new(response)
43
+ raise Errors::GoogleTwoFactorAuthenticationFailure
44
44
  else
45
45
  unless response['Token'] || response['Auth']
46
46
  raise Errors::GoogleAuthenticationFailure.new(method, response)
@@ -9,9 +9,9 @@ module Poke
9
9
  attr_reader :access_token, :provider
10
10
 
11
11
  PTC_LOGIN_URL = 'https://sso.pokemon.com/sso/login?service=https%3A%2F%2Fsso.pokemon.com' \
12
- '%2Fsso%2Foauth2.0%2FcallbackAuthorize'
13
- PTC_LOGIN_OAUTH = 'https://sso.pokemon.com/sso/oauth2.0/accessToken'
14
- PTC_LOGIN_CLIENT_SECRET = 'w8ScCUXJQc6kXKw8FiOhd8Fixzht18Dq3PEVkUCP5ZPxtgyWsbTvWHFLm2wNY0JR'
12
+ '%2Fsso%2Foauth2.0%2FcallbackAuthorize'.freeze
13
+ PTC_LOGIN_OAUTH = 'https://sso.pokemon.com/sso/oauth2.0/accessToken'.freeze
14
+ PTC_LOGIN_CLIENT_SECRET = 'w8ScCUXJQc6kXKw8FiOhd8Fixzht18Dq3PEVkUCP5ZPxtgyWsbTvWHFLm2wNY0JR'.freeze
15
15
 
16
16
  def initialize(username, password)
17
17
  @username = username
@@ -0,0 +1,51 @@
1
+ module Poke
2
+ module API
3
+ module Auth
4
+ class Ticket
5
+ attr_reader :start, :ends, :expire
6
+
7
+ def initialize
8
+ @expire = nil
9
+ @start = nil
10
+ @ends = nil
11
+ end
12
+
13
+ def has_ticket?
14
+ return true if @start && @ends && @expire
15
+ false
16
+ end
17
+
18
+ def set_ticket(auth)
19
+ @expire = auth[:expire_timestamp_ms]
20
+ @start = auth[:start]
21
+ @ends = auth[:end]
22
+ end
23
+
24
+ def is_new_ticket?(new_ticket_time)
25
+ return true unless @expire && new_ticket_time > @expire
26
+ false
27
+ end
28
+
29
+ def get_ticket
30
+ return false unless check_ticket
31
+
32
+ true
33
+ end
34
+
35
+ private
36
+
37
+ def check_ticket
38
+ return false unless has_ticket?
39
+
40
+ now_ms = (Time.now.to_f * 1000).to_i
41
+ return true if now_ms < (@expire - 10000)
42
+
43
+ @expire = nil
44
+ @start = nil
45
+ @ends = nil
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,7 +2,7 @@ module Poke
2
2
  module API
3
3
  class Client
4
4
  include Logging
5
- attr_accessor :lat, :lng, :alt, :endpoint
5
+ attr_accessor :lat, :lng, :alt, :endpoint, :ticket, :sig_path
6
6
 
7
7
  def initialize
8
8
  @auth = nil
@@ -10,7 +10,9 @@ module Poke
10
10
  @reqs = []
11
11
  @lat = 0
12
12
  @lng = 0
13
- @alt = 0
13
+ @alt = 8
14
+ @ticket = Auth::Ticket.new
15
+ @sig_path = nil
14
16
  end
15
17
 
16
18
  def login(username, password, provider)
@@ -25,12 +27,14 @@ module Poke
25
27
  raise Errors::LoginFailure.new(provider, ex)
26
28
  end
27
29
 
30
+ get_hatched_eggs
31
+ call
28
32
  logger.info "[+] Login with #{provider} Successful"
29
- fetch_endpoint
30
33
  end
31
34
 
32
35
  def call
33
36
  raise Errors::LoginRequired unless @auth
37
+ raise Errors::NoRequests if @reqs.empty?
34
38
  req = RequestBuilder.new(@auth, [@lat, @lng, @alt], @endpoint)
35
39
 
36
40
  begin
@@ -45,6 +49,15 @@ module Poke
45
49
  resp
46
50
  end
47
51
 
52
+ def activate_signature(file_path)
53
+ if File.exist?(path)
54
+ logger.info "[+] File #{file_path} has been set for signature generation"
55
+ @sig_path = path
56
+ else
57
+ raise Errors::InvalidSignatureFilePath, file_path
58
+ end
59
+ end
60
+
48
61
  def store_location(loc)
49
62
  pos = Poke::API::Helpers.get_position(loc).first
50
63
  logger.info "[+] Given location: #{pos.address}"
@@ -56,8 +69,7 @@ module Poke
56
69
 
57
70
  def store_lat_lng(lat, lng)
58
71
  logger.info "[+] Lat/Long: #{lat}, #{lng}"
59
- @lat = lat
60
- @lng = lng
72
+ @lat, @lng = lat, lng
61
73
  end
62
74
 
63
75
  def inspect
@@ -67,16 +79,6 @@ module Poke
67
79
 
68
80
  private
69
81
 
70
- def fetch_endpoint
71
- get_player
72
- get_hatched_eggs
73
- get_inventory
74
- check_awarded_badges
75
- download_settings(hash: '4a2e9bc330dae60e7b74fc85b98868ab4700802e')
76
-
77
- call
78
- end
79
-
80
82
  def method_missing(method, *args)
81
83
  POGOProtos::Networking::Requests::RequestType.const_get(method.upcase)
82
84
  @reqs << (args.empty? ? method.to_sym.upcase : { method.to_sym.upcase => args.first })
@@ -48,6 +48,24 @@ module Poke
48
48
  super("Unable to fetch endpoint, please try to login again.")
49
49
  end
50
50
  end
51
+
52
+ class NoRequests < StandardError
53
+ def initialize
54
+ super("Can not call without any requests set")
55
+ end
56
+ end
57
+
58
+ class InvalidSignatureFilePath < StandardError
59
+ def initialize(file_path)
60
+ super("Could not find file #{file_path} for signature generation")
61
+ end
62
+ end
63
+
64
+ class InvalidLevel < StandardError
65
+ def initialize(level)
66
+ super("Level #{level} is invalid, must be between 0 and 30.")
67
+ end
68
+ end
51
69
  end
52
70
  end
53
71
  end
@@ -0,0 +1,50 @@
1
+ module Poke
2
+ module API
3
+ module Geometry
4
+ module S2Base
5
+ LINEAR_PROJECTION = 0
6
+ TAN_PROJECTION = 1
7
+ QUADRATIC_PROJECTION = 2
8
+
9
+ MAX_LEVEL = 30
10
+ NUM_FACES = 6
11
+ POS_BITS = 2 * MAX_LEVEL + 1
12
+ MAX_SIZE = 1 << MAX_LEVEL
13
+ SWAP_MASK = 0x01
14
+ INVERT_MASK = 0x02
15
+ LOOKUP_BITS = 4
16
+ POS_TO_OR = [SWAP_MASK, 0, 0, INVERT_MASK | SWAP_MASK].freeze
17
+ POS_TO_IJ = [[0, 1, 3, 2],
18
+ [0, 2, 3, 1],
19
+ [3, 2, 0, 1],
20
+ [3, 1, 0, 2]].freeze
21
+
22
+ LOOKUP_POS = [nil] * (1 << (2 * LOOKUP_BITS + 2))
23
+ LOOKUP_IJ = [nil] * (1 << (2 * LOOKUP_BITS + 2))
24
+
25
+ def self.lookup_cells(level, i, j, orig_orientation, pos, orientation)
26
+ return lookup_bits(i, j, orig_orientation, pos, orientation) if level == LOOKUP_BITS
27
+
28
+ r = POS_TO_IJ[orientation]
29
+ 4.times do |index|
30
+ lookup_cells(
31
+ level + 1, (i << 1) + (r[index] >> 1), (j << 1) + (r[index] & 1),
32
+ orig_orientation, (pos << 2) + index, orientation ^ POS_TO_OR[index]
33
+ )
34
+ end
35
+ end
36
+
37
+ def self.lookup_bits(i, j, orig_orientation, pos, orientation)
38
+ ij = (i << LOOKUP_BITS) + j
39
+ LOOKUP_POS[(ij << 2) + orig_orientation] = (pos << 2) + orientation
40
+ LOOKUP_IJ[(pos << 2) + orig_orientation] = (ij << 2) + orientation
41
+ end
42
+
43
+ lookup_cells(0, 0, 0, 0, 0, 0)
44
+ lookup_cells(0, 0, 0, SWAP_MASK, 0, SWAP_MASK)
45
+ lookup_cells(0, 0, 0, INVERT_MASK, 0, INVERT_MASK)
46
+ lookup_cells(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,146 @@
1
+ module Poke
2
+ module API
3
+ module Geometry
4
+ class S2CellId
5
+ include S2Base
6
+ attr_reader :id
7
+
8
+ def initialize(id)
9
+ @id = id
10
+ end
11
+
12
+ def parent(level)
13
+ new_lsb = lsb_for_level(level)
14
+ s2 = S2CellId.new((@id & -new_lsb) | new_lsb)
15
+
16
+ raise Errors::InvalidLevel, level unless valid?(s2.id)
17
+ s2
18
+ end
19
+
20
+ def prev
21
+ S2CellId.new(@id - (lsb << 1))
22
+ end
23
+
24
+ def next
25
+ S2CellId.new(@id + (lsb << 1))
26
+ end
27
+
28
+ def level
29
+ return MAX_LEVEL if leaf?
30
+
31
+ x = (@id & 0xffffffff)
32
+ level = -1
33
+
34
+ if x != 0
35
+ level += 16
36
+ else
37
+ x = ((@id >> 32) & 0xffffffff)
38
+ end
39
+
40
+ x &= -x
41
+
42
+ level += 8 unless (x & 0x00005555).zero?
43
+ level += 4 unless (x & 0x00550055).zero?
44
+ level += 2 unless (x & 0x05050505).zero?
45
+ level += 1 unless (x & 0x11111111).zero?
46
+ level
47
+ end
48
+
49
+ def self.from_point(p)
50
+ face, u, v = xyz_to_face_uv(p)
51
+ i = st_to_ij(uv_to_st(u))
52
+ j = st_to_ij(uv_to_st(v))
53
+
54
+ S2CellId.new(from_face_ij(face, i, j))
55
+ end
56
+
57
+ def self.from_face_ij(face, i, j)
58
+ n = face << (POS_BITS - 1)
59
+ bits = face & SWAP_MASK
60
+
61
+ 7.downto(0).each do |k|
62
+ mask = (1 << LOOKUP_BITS) - 1
63
+ bits += (((i >> (k * LOOKUP_BITS)) & mask) << (LOOKUP_BITS + 2))
64
+ bits += (((j >> (k * LOOKUP_BITS)) & mask) << 2)
65
+ bits = LOOKUP_POS[bits]
66
+ n |= (bits >> 2) << (k * 2 * LOOKUP_BITS)
67
+ bits &= (SWAP_MASK | INVERT_MASK)
68
+ end
69
+
70
+ @id = (n * 2 + 1)
71
+ end
72
+
73
+ def self.xyz_to_face_uv(p)
74
+ face = p.largest_abs_component
75
+
76
+ pface = case face
77
+ when 0 then p.x
78
+ when 1 then p.y
79
+ else p.z
80
+ end
81
+
82
+ face += 3 if pface < 0
83
+
84
+ u, v = valid_face_xyz_to_uv(face, p)
85
+ [face, u, v]
86
+ end
87
+
88
+ def self.uv_to_st(u)
89
+ return 0.5 * Math.sqrt(1 + 3 * u) if u >= 0
90
+ 1 - 0.5 * Math.sqrt(1 - 3 * u)
91
+ end
92
+
93
+ def self.st_to_ij(s)
94
+ [0, [MAX_SIZE - 1, Integer((MAX_SIZE * s).floor)].min].max
95
+ end
96
+
97
+ def self.valid_face_xyz_to_uv(face, p)
98
+ raise unless p.dot_prod(face_uv_to_xyz(face, 0, 0)) > 0
99
+
100
+ case face
101
+ when 0 then [p.y / p.x, p.z / p.x]
102
+ when 1 then [-p.x / p.y, p.z / p.y]
103
+ when 2 then [-p.x / p.z, -p.y / p.z]
104
+ when 3 then [p.z / p.x, p.y / p.x]
105
+ when 4 then [p.z / p.y, -p.x / p.y]
106
+ else [-p.y / p.z, -p.x / p.z]
107
+ end
108
+ end
109
+
110
+ def self.face_uv_to_xyz(face, u, v)
111
+ case face
112
+ when 0 then S2Point.new(1, u, v)
113
+ when 1 then S2Point.new(-u, 1, v)
114
+ when 2 then S2Point.new(-u, -v, 1)
115
+ when 3 then S2Point.new(-1, -v, -u)
116
+ when 4 then S2Point.new(v, -1, -u)
117
+ else S2Point.new(v, u, -1)
118
+ end
119
+ end
120
+
121
+ private_class_method :face_uv_to_xyz, :valid_face_xyz_to_uv, :uv_to_st,
122
+ :st_to_ij, :xyz_to_face_uv, :from_face_ij
123
+
124
+ private
125
+
126
+ def leaf?
127
+ @id & 1 != 0
128
+ end
129
+
130
+ def valid?(s2_id)
131
+ face = s2_id >> POS_BITS
132
+ lsb = s2_id & -s2_id
133
+ (face < NUM_FACES) && (lsb & 0x1555555555555555) != 0
134
+ end
135
+
136
+ def lsb
137
+ @id & -@id
138
+ end
139
+
140
+ def lsb_for_level(level)
141
+ 1 << (2 * (MAX_LEVEL - level))
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,19 @@
1
+ module Poke
2
+ module API
3
+ module Geometry
4
+ class S2LatLon
5
+ def initialize(lat_degrees, lon_degrees)
6
+ @lat = lat_degrees * Math::PI / 180
7
+ @lon = lon_degrees * Math::PI / 180
8
+ end
9
+
10
+ def to_point
11
+ phi = @lat
12
+ theta = @lon
13
+ cosphi = Math.cos(phi)
14
+ S2Point.new(Math.cos(theta) * cosphi, Math.sin(theta) * cosphi, Math.sin(phi))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ module Poke
2
+ module API
3
+ module Geometry
4
+ class S2Point
5
+ attr_reader :x, :y, :z
6
+
7
+ def initialize(x, y, z)
8
+ @x = x
9
+ @y = y
10
+ @z = z
11
+ end
12
+
13
+ def abs
14
+ [@x.abs, @y.abs, @z.abs]
15
+ end
16
+
17
+ def largest_abs_component
18
+ temp = abs
19
+
20
+ if temp[0] > temp[1]
21
+ temp[0] > temp[2] ? 0 : 2
22
+ else
23
+ temp[1] > temp[2] ? 1 : 2
24
+ end
25
+ end
26
+
27
+ def dot_prod(o)
28
+ @x * o.x + @y * o.y + @z * o.z
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,4 +1,5 @@
1
1
  require 'geocoder'
2
+ require 'ruby-xxhash'
2
3
 
3
4
  module Poke
4
5
  module API
@@ -11,6 +12,29 @@ module Poke
11
12
  def camel_case_lower(sym)
12
13
  sym.to_s.split('_').collect(&:capitalize).join
13
14
  end
15
+
16
+ def generate_location_one(auth_ticket, pos)
17
+ first_hash = XXhash.xxh32(auth_ticket, 0x1B845238)
18
+ location_bytes = d2h(pos[0]) + d2h(pos[1]) + d2h(pos[2])
19
+
20
+ XXhash.xxh32(location_bytes, first_hash)
21
+ end
22
+
23
+ def generate_location_two(pos)
24
+ location_bytes = d2h(pos[0]) + d2h(pos[1]) + d2h(pos[2])
25
+
26
+ XXhash.xxh32(location_bytes, seed=0x1B845238)
27
+ end
28
+
29
+ def generate_request(auth_ticket, request)
30
+ first_hash = XXhash.xxh64(auth_ticket, 0x1B845238)
31
+
32
+ XXhash.xxh64(request, first_hash)
33
+ end
34
+
35
+ def d2h(input)
36
+ [input].pack('d').unpack('Q').first.to_s(16).split.pack('H*')
37
+ end
14
38
  end
15
39
  end
16
40
  end
@@ -1,6 +1,10 @@
1
+ require 'ffi'
2
+ require 'securerandom'
3
+
1
4
  module Poke
2
5
  module API
3
6
  class RequestBuilder
7
+ extend FFI::Library
4
8
  include Logging
5
9
 
6
10
  def initialize(auth, pos, endpoint)
@@ -9,11 +13,14 @@ module Poke
9
13
  @endpoint = endpoint
10
14
  @position = pos
11
15
  @client = HTTPClient.new(agent_name: 'PokeAPI/0.0.1')
16
+ @start_time = (Time.now.to_f * 1000).to_i
17
+ @ffi_enabled = false
12
18
  end
13
19
 
14
20
  def request(reqs, client)
15
21
  logger.debug '[+] Creating new request'
16
- request_proto = build_main_request(reqs)
22
+ request_proto = build_main_request(reqs, client)
23
+ logger.debug "[+] Generated RPC protobuf encoded request \r\n#{request_proto.inspect}"
17
24
 
18
25
  logger.info '[+] Executing RPC request'
19
26
  resp = execute_rpc_request(request_proto)
@@ -26,7 +33,7 @@ module Poke
26
33
 
27
34
  private
28
35
 
29
- def build_main_request(sub_reqs)
36
+ def build_main_request(sub_reqs, client)
30
37
  request_envelope = POGOProtos::Networking::Envelopes::RequestEnvelope
31
38
  req = request_envelope.new(
32
39
  status_code: 2,
@@ -35,15 +42,85 @@ module Poke
35
42
  )
36
43
  req.latitude, req.longitude, req.altitude = @position
37
44
 
38
- token = request_envelope::AuthInfo::JWT.new(contents: @access_token, unknown2: 59)
39
- req.auth_info = request_envelope::AuthInfo.new(provider: @provider, token: token)
40
-
41
45
  build_sub_request(req, sub_reqs)
46
+ set_authentication(req, client, request_envelope)
42
47
 
43
48
  logger.debug "[+] Generated RPC protobuf request \r\n#{req.inspect}"
44
49
  req.to_proto
45
50
  end
46
51
 
52
+ def set_authentication(req, client, request_envelope)
53
+ if client.ticket.get_ticket
54
+ logger.info '[+] Using Auth ticket'
55
+
56
+ req.auth_ticket = POGOProtos::Networking::Envelopes::AuthTicket.new(
57
+ start: client.ticket.start, end: client.ticket.ends,
58
+ expire_timestamp_ms: client.ticket.expire
59
+ )
60
+
61
+ load_library(client.sig_path) if client.sig_path && !@ffi_enabled
62
+ set_signature(req, request_envelope) if @ffi_enabled
63
+ else
64
+ logger.info '[+] Using Provider token'
65
+ token = request_envelope::AuthInfo::JWT.new(contents: @access_token, unknown2: 59)
66
+ req.auth_info = request_envelope::AuthInfo.new(provider: @provider, token: token)
67
+ end
68
+ end
69
+
70
+ def set_signature(req, _request_envelope)
71
+ signature = Signature.new(
72
+ location_hash1: Helpers.generate_location_one(req.auth_ticket.to_proto, @position),
73
+ location_hash2: Helpers.generate_location_two(@position),
74
+ unk22: SecureRandom.random_bytes(32),
75
+ timestamp: (Time.now.to_f * 1000).to_i,
76
+ timestamp_since_start: (Time.now.to_f * 1000).to_i - @start_time
77
+ )
78
+
79
+ req.requests.each do |r|
80
+ signature.request_hash << Helpers.generate_request(req.auth_ticket.to_proto, r.to_proto)
81
+ end
82
+
83
+ unknown6 = POGOProtos::Networking::Envelopes::Unknown6
84
+
85
+ req.unknown6 = unknown6.new(
86
+ unknown1: 6,
87
+ unknown2: unknown6::Unknown2.new(unknown1: generate_signature(signature.to_proto))
88
+ )
89
+
90
+ logger.info '[+] Setting Signature'
91
+ end
92
+
93
+ def generate_signature(signature)
94
+ attempts = 5
95
+
96
+ begin
97
+ logger.info '[+] Generating Signature'
98
+ output_size = FFI::MemoryPointer.new(:size_t)
99
+
100
+ iv = SecureRandom.random_bytes(32)
101
+ RequestBuilder.encrypt(signature, signature.length, iv, 32, nil, output_size)
102
+
103
+ output = FFI::MemoryPointer.new(output_size.read_int)
104
+ RequestBuilder.encrypt(signature, signature.length, iv, 32, output, output_size)
105
+ rescue StandardError => ex
106
+ raise ex if (attempts -= 1) == 0
107
+ retry
108
+ end
109
+
110
+ output.read_string
111
+ end
112
+
113
+ def load_library(path)
114
+ RequestBuilder.ffi_lib(path)
115
+ RequestBuilder.attach_function(
116
+ :encrypt,
117
+ [:string, :size_t, :string, :size_t, :pointer, :pointer],
118
+ :int
119
+ )
120
+
121
+ @ffi_enabled = true
122
+ end
123
+
47
124
  def build_sub_request(req, sub_reqs)
48
125
  sub_reqs.each do |sub_req|
49
126
  if sub_req.is_a?(Symbol)
@@ -16,6 +16,9 @@ module Poke
16
16
  @response = POGOProtos::Networking::Envelopes::ResponseEnvelope.decode(@response)
17
17
  logger.debug "[+] Decoded RPC response \r\n#{@response.inspect}"
18
18
 
19
+ store_ticket(client)
20
+ store_endpoint(client)
21
+
19
22
  logger.info '[+] Decoding Sub RPC responses'
20
23
  decoded_resp = parse_rpc_fields(decode_sub_responses)
21
24
 
@@ -24,7 +27,6 @@ module Poke
24
27
  parse_rpc_fields(decoded_resp)
25
28
  end
26
29
 
27
- store_endpoint(client)
28
30
  decoded_resp.merge!(status_code: @response.status_code,
29
31
  api_url: @response.api_url, error: @response.error)
30
32
 
@@ -34,6 +36,17 @@ module Poke
34
36
 
35
37
  private
36
38
 
39
+ def store_ticket(client)
40
+ return unless @response.auth_ticket
41
+
42
+ auth = @response.auth_ticket.to_hash
43
+
44
+ if client.ticket.is_new_ticket?(auth[:expire_timestamp_ms])
45
+ logger.debug "[+] Storing Auth Ticket\r\n#{auth}"
46
+ client.ticket.set_ticket(auth)
47
+ end
48
+ end
49
+
37
50
  def store_endpoint(client)
38
51
  logger.debug "[+] Current Endpoint #{client.endpoint}"
39
52
 
@@ -43,7 +56,7 @@ module Poke
43
56
 
44
57
  return if @response.api_url.empty?
45
58
 
46
- logger.debug "[+] Setting Endpoint to #{@response.api_url}"
59
+ logger.debug "[+] Setting Endpoint to https://#{@response.api_url}/rpc"
47
60
  client.endpoint = "https://#{@response.api_url}/rpc"
48
61
  end
49
62
 
@@ -1,24 +1,37 @@
1
1
  # Load Poke-API related classes
2
2
  require 'pp'
3
- require 'pry'
4
- require_relative 'logging'
5
- require_relative 'client'
6
- require_relative 'helpers'
7
- require_relative 'request_builder'
8
- require_relative 'response'
9
- require_relative 'errors'
10
- require_relative 'version'
11
- require_relative 'auth/ptc'
12
- require_relative 'auth/google'
3
+ require 'poke-api'
13
4
 
14
- # Load Google Generated POGOProtos
15
- require_relative 'POGOProtos/Networking/Envelopes/RequestEnvelope'
16
- require_relative 'POGOProtos/Networking/Envelopes/ResponseEnvelope'
5
+ def get_cells(lat, lng, radius = 10)
6
+ s2_point = Poke::API::Geometry::S2LatLon.new(lat, lng).to_point
7
+ s2_cell = Poke::API::Geometry::S2CellId.from_point(s2_point).parent(15)
17
8
 
9
+ next_cell = s2_cell.next
10
+ prev_cell = s2_cell.prev
11
+
12
+ radius.times.reduce([s2_cell.id]) do |acc, el|
13
+ acc += [next_cell.id, prev_cell.id]
14
+ next_cell = next_cell.next
15
+ prev_cell = prev_cell.prev
16
+ acc
17
+ end.sort
18
+ end
19
+
20
+ #Poke::API::Logging.log_level = :DEBUG
18
21
  client = Poke::API::Client.new
19
- Poke::API::Logging.log_level = :DEBUG
20
- #client.store_lat_lng(55.864237, -4.251806)
22
+ client.activate_signature('encrypt64bit.dll')
23
+
24
+ client.store_location('Glasgow')
21
25
  client.login('srn720', 'illi0wnz', 'ptc')
22
- #client.get_player
23
- #resp = client.call
24
- #pp resp
26
+
27
+ #Get cells
28
+ cell_ids = get_cells(client.lat, client.lng)
29
+
30
+ # Construct map objects call
31
+ client.get_map_objects(
32
+ latitude: client.lat,
33
+ longitude: client.lng,
34
+ since_timestamp_ms: [0] * cell_ids.length,
35
+ cell_id: cell_ids
36
+ )
37
+ p client.call
@@ -1,5 +1,5 @@
1
1
  module Poke
2
2
  module API
3
- VERSION = '0.0.8'.freeze
3
+ VERSION = '0.1.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poke-go-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nabeel Amjad
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-03 00:00:00.000000000 Z
11
+ date: 2016-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.1.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: ruby-xxHash
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.4.0.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.4.0.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: ffi
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.9.14
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 1.9.14
69
97
  description: An API wrapper for Pokemon GO
70
98
  email:
71
99
  executables: []
@@ -294,6 +322,7 @@ files:
294
322
  - lib/poke-api/POGOProtos/Settings/Master/PokemonSettings.rb
295
323
  - lib/poke-api/POGOProtos/Settings/Master/PokemonUpgradeSettings.rb
296
324
  - lib/poke-api/POGOProtos/Settings/Master/TypeEffectiveSettings.rb
325
+ - lib/poke-api/POGOProtos/Signature.rb
297
326
  - lib/poke-api/POGOProtosSource/Data/AssetDigestEntry.proto
298
327
  - lib/poke-api/POGOProtosSource/Data/Battle/BattleAction.proto
299
328
  - lib/poke-api/POGOProtosSource/Data/Battle/BattleActionType.proto
@@ -518,10 +547,16 @@ files:
518
547
  - lib/poke-api/POGOProtosSource/Settings/Master/PokemonSettings.proto
519
548
  - lib/poke-api/POGOProtosSource/Settings/Master/PokemonUpgradeSettings.proto
520
549
  - lib/poke-api/POGOProtosSource/Settings/Master/TypeEffectiveSettings.proto
550
+ - lib/poke-api/POGOProtosSource/Signature.proto
521
551
  - lib/poke-api/auth/google.rb
522
552
  - lib/poke-api/auth/ptc.rb
553
+ - lib/poke-api/auth/ticket.rb
523
554
  - lib/poke-api/client.rb
524
555
  - lib/poke-api/errors.rb
556
+ - lib/poke-api/geometry/s2_base.rb
557
+ - lib/poke-api/geometry/s2_cell_id.rb
558
+ - lib/poke-api/geometry/s2_lat_lon.rb
559
+ - lib/poke-api/geometry/s2_point.rb
525
560
  - lib/poke-api/helpers.rb
526
561
  - lib/poke-api/logging.rb
527
562
  - lib/poke-api/request_builder.rb