pingo 1.0.0 → 1.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: 8e46197fce732659a1f29c737099474746d885f9
4
- data.tar.gz: 543c8ec0bbdf0cdd48eeadb1b246d93ba427b6b4
3
+ metadata.gz: 371a37cd9c400853c5fa4e23e4fbe33ca6c1665c
4
+ data.tar.gz: 38d4970b71fc6c5bf5ae37e3b29ff219e6e0b6ee
5
5
  SHA512:
6
- metadata.gz: 2bf5b2aa38d6c5441c256831ddcfa187948823cc99a05732ee635574adba466e273688bbc07932fb32b14ecad37cc11f918bb9b08fa1d0f757a9ab8f2a3cb6b3
7
- data.tar.gz: 95e36f1837a12ca45133480a4309bc1c9ff287e8854adc6f8f819b051c3956df103572bda8c653e130b89c7932731543c23431a1adcd5bed6b2239308406ef53
6
+ metadata.gz: 723cb94874b30b16762d4d40ad8bf3f0b5c1ee8fdd7d096a95f06882e60b7a86aa8735372e9eb6ebb098c52c50cc16c51ef6f0382f86f5e740d6ca92d8e675da
7
+ data.tar.gz: 316322f0d1f131526dc3feca391ccee749c3e185a54ed5afe127fc19b5e4c322f056603a81455cd2f937879c2dff780e0298de1ef7790e635412236c9f9910de
@@ -1,3 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.1
3
+ - 2.4.1
4
+ - 2.3.4
5
+ - 2.2.7
6
+ - 2.1.10
7
+ - 2.0.0
8
+ - ruby-head
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Pingo [![Gem Version](https://badge.fury.io/rb/pingo.svg)](http://badge.fury.io/rb/pingo) [![Code Climate](https://codeclimate.com/github/Kyuden/pingo/badges/gpa.svg)](https://codeclimate.com/github/Kyuden/pingo) [![wercker status](https://app.wercker.com/status/8fc989959ae4630aef746364c6ead94f/m/master "wercker status")](https://app.wercker.com/project/bykey/8fc989959ae4630aef746364c6ead94f)
1
+ # Pingo [![Gem Version](https://badge.fury.io/rb/pingo.svg)](http://badge.fury.io/rb/pingo) [![Code Climate](https://codeclimate.com/github/Kyuden/pingo/badges/gpa.svg)](https://codeclimate.com/github/Kyuden/pingo)
2
2
 
3
3
  <p><img width="400"src="http://www.fastpic.jp/images.php?file=8622347177.jpg"></p>
4
4
  Pingo provide a scatterbrain with a `pingo` command of sounding your iphone.
@@ -7,11 +7,19 @@ Pingo provide a scatterbrain with a `pingo` command of sounding your iphone.
7
7
 
8
8
  Install it yourself as:
9
9
 
10
- $ gem install pingo
10
+ ```bash
11
+ $ gem install pingo
12
+ ```
13
+
14
+ If you've done Go development before and your $GOPATH/bin directory is already in your PATH, this is an alternative installation method that fetches `pingo` into your GOPATH and builds it automatically:
15
+
16
+ ```
17
+ $ go get github.com/kyuden/pingo
18
+ ```
11
19
 
12
20
  ## Usage
13
21
 
14
- Set APPLE_ID and APPLE_PASSWORD in environment variables.
22
+ Set APPLE_ID and APPLE_PASSWORD in environment variables.
15
23
 
16
24
  ```bash
17
25
  # Set it in .zshrc, .bashrc etc...
@@ -1,20 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pingo/version"
2
4
  require "pingo/cli"
3
5
  require "json"
4
6
  require "typhoeus"
5
7
 
6
8
  module Pingo
7
- class Pingo
8
- INIT_CLIENT = 'initClient'
9
- PLAY_SOUND = 'playSound'
9
+ class Client
10
+ INIT_CLIENT = 'initClient'.freeze
11
+ PLAY_SOUND = 'playSound'.freeze
10
12
 
11
13
  class << self
12
14
  def run(model_name)
13
- new(model_name).instance_eval do
14
- partition = request_partition
15
- device_ids = request_device_ids(partition)
16
- request_sound(partition, device_ids)
17
- end
15
+ new(model_name).sound!
18
16
  end
19
17
  end
20
18
 
@@ -24,38 +22,35 @@ module Pingo
24
22
  @password = ENV['APPLE_PASSWORD']
25
23
  end
26
24
 
27
- private
28
- def request_partition
29
- post(INIT_CLIENT).headers['X-Apple-MMe-Host']
30
- end
25
+ def sound!
26
+ device_ids.each { |device_id| post(PLAY_SOUND, generate_body(device_id)) }
27
+ end
31
28
 
32
- def request_device_ids(partition)
33
- raise "partition is nil" unless partition
34
- parse_device_ids(post(INIT_CLIENT, partition))
35
- end
29
+ def device_ids
30
+ response = post(INIT_CLIENT)
31
+ raise "#{response.response_code}:#{response.status_message }" unless response.success?
32
+ parse_device_ids(response.body)
33
+ end
34
+
35
+ private
36
36
 
37
- def parse_device_ids(data)
38
- target_contents(data).map { |content| content["id"] }
37
+ def parse_device_ids(body)
38
+ target_contents(body).map { |content| content["id"] }
39
39
  end
40
40
 
41
- def target_contents(data)
42
- contents(data).find_all { |content| match_device?(content) }
41
+ def target_contents(body)
42
+ target = contents(body).find_all { |content| match_device?(content) }
43
+ target.empty? ? raise("Not found your device(iPhone#{@model_name})") : target
43
44
  end
44
45
 
45
- def contents(data)
46
- JSON.parse(data.body)['content']
46
+ def contents(body)
47
+ JSON.parse(body)['content']
47
48
  end
48
49
 
49
50
  def match_device?(params)
50
51
  params['deviceDisplayName'] =~ /#{@model_name}$/i
51
52
  end
52
53
 
53
- def request_sound(partition, device_ids)
54
- raise "partition is nil" unless partition
55
- raise "device id is nil" if Array(device_ids).empty?
56
- Array(device_ids).map { |device_id| post(PLAY_SOUND, partition, generate_body(device_id)) }
57
- end
58
-
59
54
  def generate_body(device_id)
60
55
  JSON.generate(sound_body(device_id))
61
56
  end
@@ -72,8 +67,8 @@ module Pingo
72
67
  }
73
68
  end
74
69
 
75
- def post(mode, partition="fmipmobile.icloud.com", body=nil)
76
- Typhoeus::Request.post(uri(mode, partition),
70
+ def post(type, body=nil)
71
+ Typhoeus::Request.post(uri(type),
77
72
  userpwd: "#{@username}:#{@password}",
78
73
  headers: post_headers,
79
74
  followlocation: true,
@@ -94,8 +89,8 @@ module Pingo
94
89
  }
95
90
  end
96
91
 
97
- def uri(mode, partition)
98
- "https://#{partition}/fmipservice/device/#{@username}/#{mode}"
92
+ def uri(type)
93
+ "https://fmipmobile.icloud.com/fmipservice/device/#{@username}/#{type}"
99
94
  end
100
95
  end
101
96
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'pingo/cli/pingo'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pingo
2
4
  class CLI < Thor
3
5
  desc "[MODEL_NAME]", "Sound apple device"
@@ -9,7 +11,7 @@ module Pingo
9
11
  LONGDESC
10
12
 
11
13
  def pingo(model_name)
12
- Pingo.run(model_name)
14
+ Client.run(model_name)
13
15
  end
14
16
  end
15
17
  end
@@ -1,3 +1,3 @@
1
1
  module Pingo
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/pin.go ADDED
@@ -0,0 +1,299 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "errors"
7
+ "flag"
8
+ "fmt"
9
+ "io"
10
+ "io/ioutil"
11
+ "net/http"
12
+ "os"
13
+ "strconv"
14
+ "strings"
15
+ "time"
16
+ )
17
+
18
+ const (
19
+ AppName = "Pingo"
20
+ APIName = "FindMyiphone"
21
+ APIVertion = "2.0.2"
22
+ )
23
+
24
+ const (
25
+ ExitOK = 0 + iota
26
+
27
+ ExitError = 9 + iota
28
+ ExitParseArgsError
29
+ ExitRequestDeviceError
30
+ ExitRequestSoundError
31
+ )
32
+
33
+ var HEADER_MAP = map[string][]string{
34
+ "Content-Type": {"application/json; charset=utf-8"},
35
+ "X-Apple-Find-Api-Ver": {"2.0"},
36
+ "X-Apple-Authscheme": {"UserIdGuest"},
37
+ "X-Apple-Realm-Support": {"1.0"},
38
+ "Accept-Language": {"en-us"},
39
+ "userAgent": {"Pingo"},
40
+ "Connection": {"keep-alive"},
41
+ }
42
+
43
+ type ClientContext struct {
44
+ AppName string `json:"appName"`
45
+ AppVersion string `json:"appVersion"`
46
+ ShouldLocate bool `json:"shouldLocate"`
47
+ }
48
+
49
+ type SoundParams struct {
50
+ ClientContext `json:"clientContext"`
51
+ Device string `json:"device"`
52
+ Subject string `json:"subject"`
53
+ }
54
+
55
+ type Container struct {
56
+ Content []struct {
57
+ DeviceName string `json:"deviceDisplayName"`
58
+ DeviceId string `json:"id"`
59
+ } `json:"content"`
60
+ }
61
+
62
+ func main() {
63
+ os.Exit(NewCLI().Run(os.Args))
64
+ }
65
+
66
+ type CLI struct {
67
+ outStream, errStream io.Writer
68
+ }
69
+
70
+ func NewCLI() *CLI {
71
+ return &CLI{
72
+ outStream: os.Stdout,
73
+ errStream: os.Stderr,
74
+ }
75
+ }
76
+
77
+ func (cli *CLI) PutOutStream(format string, args ...interface{}) {
78
+ fmt.Fprintf(cli.outStream, format, args...)
79
+ }
80
+
81
+ func (cli *CLI) PutErrStream(format string, args ...interface{}) {
82
+ fmt.Fprintf(cli.errStream, format, args...)
83
+ }
84
+
85
+ func (cli *CLI) Run(args []string) int {
86
+ appleAccount, err := cli.parseArgs(args)
87
+ if err != nil {
88
+ cli.PutErrStream("Failed to parse args:\n %s\n", err)
89
+ return ExitParseArgsError
90
+ }
91
+
92
+ client := &Client{AppleAccount: appleAccount}
93
+
94
+ deviceID, err := client.RequestDeviceID()
95
+ if err != nil {
96
+ cli.PutErrStream("Failed to request device id:\n %s\n", err)
97
+ return ExitRequestDeviceError
98
+ }
99
+
100
+ if err = client.RequestSound(deviceID); err != nil {
101
+ cli.PutErrStream("Failed to request sound:\n %s\n", err)
102
+ return ExitRequestSoundError
103
+ }
104
+
105
+ cli.PrintSuccessMessage()
106
+
107
+ return ExitOK
108
+ }
109
+
110
+ type AppleAccount struct {
111
+ ID string
112
+ Pass string
113
+ ModelName string
114
+ }
115
+
116
+ func (cli *CLI) parseArgs(args []string) (*AppleAccount, error) {
117
+ appleID := os.Getenv("APPLE_ID")
118
+ applePass := os.Getenv("APPLE_PASSWORD")
119
+
120
+ flags := flag.NewFlagSet(AppName, flag.ContinueOnError)
121
+
122
+ flags.StringVar(&appleID, "apple-id", appleID, "apple id to use")
123
+ flags.StringVar(&appleID, "i", appleID, "apple id to use (short)")
124
+ flags.StringVar(&applePass, "apple-password", applePass, "apple passwaord to to")
125
+ flags.StringVar(&applePass, "p", applePass, "apple passwaord to to (short)")
126
+
127
+ if err := flags.Parse(args[1:]); err != nil {
128
+ return nil, errors.New("Faild to parse flag")
129
+ }
130
+
131
+ if appleID == "" || applePass == "" {
132
+ return nil, errors.New("APPLE ID or APPLE PASSWORD are empty")
133
+ }
134
+
135
+ modelName := flags.Arg(0)
136
+
137
+ if modelName == "" {
138
+ return nil, errors.New("Device model name is empty")
139
+ }
140
+
141
+ return &AppleAccount{ID: appleID, Pass: applePass, ModelName: modelName}, nil
142
+ }
143
+
144
+ func (c *CLI) PrintSuccessMessage() {
145
+ fmt.Println(`
146
+ 888888ba oo
147
+ 88 8b
148
+ a88aaaa8P dP 88d888b. .d8888b. .d8888b.
149
+ 88 88 88 88 88 88 88 88
150
+ 88 88 88 88 88. .88 88. .88
151
+ dP dP dP dP .8888P88 88888P
152
+ .88
153
+ d8888P
154
+ `)
155
+ return
156
+ }
157
+
158
+ type Client struct {
159
+ *AppleAccount
160
+ debug bool
161
+ }
162
+
163
+ func (c *Client) requestDeviceIDURL() string {
164
+ return fmt.Sprintf("https://fmipmobile.icloud.com/fmipservice/device/%s/initClient", c.ModelName)
165
+ }
166
+
167
+ func (c *Client) requestSoundURL() string {
168
+ return fmt.Sprintf("https://fmipmobile.icloud.com/fmipservice/device/%s/playSound", c.ModelName)
169
+ }
170
+
171
+ func (c *Client) RequestDeviceID() (string, error) {
172
+ body, err := c.getBody("POST", c.requestDeviceIDURL(), nil)
173
+ if err != nil {
174
+ return "", errors.New("getBody: " + err.Error())
175
+ }
176
+
177
+ deviceID, err := c.parseDeviceID(body)
178
+ if err != nil {
179
+ return "", errors.New("parseDeviceID: " + err.Error())
180
+ }
181
+
182
+ return deviceID, nil
183
+ }
184
+
185
+ func (c *Client) getBody(method string, url string, params io.Reader) ([]byte, error) {
186
+ resp, err := c.httpExecute(method, url, params)
187
+ if err != nil {
188
+ return nil, errors.New("httpExecute: " + err.Error())
189
+ }
190
+
191
+ bodyBytes, err := ioutil.ReadAll(resp.Body)
192
+ defer resp.Body.Close()
193
+ if err != nil {
194
+ return nil, errors.New("ReadAll: " + err.Error())
195
+ }
196
+
197
+ if c.debug {
198
+ fmt.Printf("STATUS: %s\n", resp.Status)
199
+ fmt.Println("BODY RESPONSE: " + string(bodyBytes))
200
+ }
201
+
202
+ return bodyBytes, nil
203
+ }
204
+
205
+ type HTTPExecuteError struct {
206
+ RequestHeaders string
207
+ ResponseBodyBytes []byte
208
+ Status string
209
+ StatusCode int
210
+ }
211
+
212
+ func (e HTTPExecuteError) Error() string {
213
+ return "HTTP response is not 200/OK as expected. Actual response: \n" +
214
+ "\tResponse Status: '" + e.Status + "'\n" +
215
+ "\tResponse Code: " + strconv.Itoa(e.StatusCode) + "\n" +
216
+ "\tRequest Headers: " + e.RequestHeaders + "\n" +
217
+ "\tResponse Body: " + string(e.ResponseBodyBytes)
218
+ }
219
+
220
+ func (c *Client) httpExecute(method string, url string, body io.Reader) (*http.Response, error) {
221
+ req, err := http.NewRequest(method, url, body)
222
+ if err != nil {
223
+ return nil, errors.New("NewRequest: " + err.Error())
224
+ }
225
+
226
+ req.Header = http.Header(HEADER_MAP)
227
+ req.SetBasicAuth(c.ID, c.Pass)
228
+
229
+ client := &http.Client{Timeout: time.Duration(10 * time.Second)}
230
+
231
+ if c.debug {
232
+ fmt.Printf("Request: %v\n", req)
233
+ }
234
+ resp, err := client.Do(req)
235
+ if err != nil {
236
+ return nil, errors.New("Do: " + err.Error())
237
+ }
238
+
239
+ if resp.StatusCode != 200 {
240
+ defer resp.Body.Close()
241
+ bytes, _ := ioutil.ReadAll(resp.Body)
242
+
243
+ debugHeader := ""
244
+ for k, vals := range req.Header {
245
+ for _, val := range vals {
246
+ debugHeader += "[key: " + k + ", val: " + val + "]"
247
+ }
248
+ }
249
+
250
+ return resp, HTTPExecuteError{
251
+ RequestHeaders: debugHeader,
252
+ ResponseBodyBytes: bytes,
253
+ Status: resp.Status,
254
+ StatusCode: resp.StatusCode,
255
+ }
256
+ }
257
+
258
+ return resp, nil
259
+ }
260
+
261
+ func (c *Client) parseDeviceID(body []byte) (string, error) {
262
+
263
+ var cont Container
264
+ if err := json.Unmarshal(body, &cont); err != nil {
265
+ return "", errors.New("Unmarshal: " + err.Error())
266
+ }
267
+
268
+ var deviceId string
269
+ for _, v := range cont.Content {
270
+ if strings.HasSuffix(v.DeviceName, c.ModelName) {
271
+ deviceId = v.DeviceId
272
+ break
273
+ }
274
+ }
275
+
276
+ if deviceId == "" {
277
+ return "", errors.New("Not found device id")
278
+ }
279
+
280
+ return deviceId, nil
281
+ }
282
+
283
+ func (c *Client) RequestSound(deviceId string) error {
284
+ input, err := json.Marshal(SoundParams{
285
+ ClientContext: ClientContext{AppName: APIName, AppVersion: APIVertion},
286
+ Device: deviceId,
287
+ Subject: AppName,
288
+ })
289
+
290
+ if err != nil {
291
+ return errors.New("json.Marshal: " + err.Error())
292
+ }
293
+
294
+ if _, err := c.getBody("POST", c.requestSoundURL(), bytes.NewBuffer(input)); err != nil {
295
+ return errors.New("getBody: " + err.Error())
296
+ }
297
+
298
+ return nil
299
+ }