poke-go-api 0.0.8 → 0.1.0

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