2f0dcd1ac2
Squashed commit of the following: commit 0eb23aa87c264152716933e03827f040742e6d70 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Feb 20 14:21:48 2016 -0800 Updating readme to reflect updated flow. commit 840268e2db6093b5cb573c6a3e71204bf5b08b48 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Feb 20 14:18:39 2016 -0800 Dropping python 2.6 support workaround. commit 9dcbd460482c2925bda490be2be884a2a2526062 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Feb 20 14:18:00 2016 -0800 Adding additional behavior at @torarnv's request. commit 6c711bb12beea7c792b5d386203373423b6e56e2 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 15:08:29 2016 -0800 Workaround for obsolete versions of Python 2. commit b0765b7b6bf9974348061043da9a110c6bd7d985 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 14:56:53 2016 -0800 Style changes to avoid line length overage. commit 4decc576432ef23edae01b9621f2689b4f3c6c84 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 14:01:27 2016 -0800 Adding documentation; also adding --delete-from-keyring command-line option. commit a6b0224e93a8bc9159cf06ba5792a384f7fbb060 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 13:44:09 2016 -0800 Adding functionality allowing authentication using iCloud passwords stored in the system keychain. Adds the following new command-line options: * `--password-interactive`: Allows you to specify your password interactively rather than typing it into the command-line. * `--store-in-keychain`: Allows you to store the password in use in the system keychain. If no password is specified when instantiating `PyiCloudService` or when using the command-line utility (via either `--password-interactive` or `--password`), the system keychain will be queried for a stored password, and an exception will be raised if one was not found. commit 4ba03fb02d51673dfb7183dde49ab4c0bec4afb3 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 13:43:39 2016 -0800 Removing unused imports.
328 lines
9.7 KiB
Python
328 lines
9.7 KiB
Python
#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
A Command Line Wrapper to allow easy use of pyicloud for
|
|
command line scripts, and related.
|
|
"""
|
|
from __future__ import print_function
|
|
import argparse
|
|
import pickle
|
|
import sys
|
|
|
|
from click import confirm
|
|
|
|
import pyicloud
|
|
from . import utils
|
|
|
|
|
|
DEVICE_ERROR = (
|
|
"Please use the --device switch to indicate which device to use."
|
|
)
|
|
|
|
|
|
def create_pickled_data(idevice, filename):
|
|
"""This helper will output the idevice to a pickled file named
|
|
after the passed filename.
|
|
|
|
This allows the data to be used without resorting to screen / pipe
|
|
scrapping. """
|
|
data = {}
|
|
for x in idevice.content:
|
|
data[x] = idevice.content[x]
|
|
location = filename
|
|
pickle_file = open(location, 'wb')
|
|
pickle.dump(data, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
|
|
pickle_file.close()
|
|
|
|
|
|
def main(args=None):
|
|
"""Main commandline entrypoint"""
|
|
if args is None:
|
|
args = sys.argv[1:]
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Find My iPhone CommandLine Tool")
|
|
|
|
parser.add_argument(
|
|
"--username",
|
|
action="store",
|
|
dest="username",
|
|
default="",
|
|
help="Apple ID to Use"
|
|
)
|
|
parser.add_argument(
|
|
"--password",
|
|
action="store",
|
|
dest="password",
|
|
default="",
|
|
help=(
|
|
"Apple ID Password to Use; if unspecified, password will be "
|
|
"fetched from the system keyring."
|
|
)
|
|
)
|
|
parser.add_argument(
|
|
"-n",
|
|
"--non-interactive",
|
|
action="store_false",
|
|
dest="interactive",
|
|
default=True,
|
|
help="Disable interactive prompts."
|
|
)
|
|
parser.add_argument(
|
|
"--delete-from-keyring",
|
|
action="store_true",
|
|
dest="delete_from_keyring",
|
|
default=False,
|
|
help="Delete stored password in system keyring for this username.",
|
|
)
|
|
parser.add_argument(
|
|
"--list",
|
|
action="store_true",
|
|
dest="list",
|
|
default=False,
|
|
help="Short Listings for Device(s) associated with account",
|
|
)
|
|
parser.add_argument(
|
|
"--llist",
|
|
action="store_true",
|
|
dest="longlist",
|
|
default=False,
|
|
help="Detailed Listings for Device(s) associated with account",
|
|
)
|
|
parser.add_argument(
|
|
"--locate",
|
|
action="store_true",
|
|
dest="locate",
|
|
default=False,
|
|
help="Retrieve Location for the iDevice (non-exclusive).",
|
|
)
|
|
|
|
# Restrict actions to a specific devices UID / DID
|
|
parser.add_argument(
|
|
"--device",
|
|
action="store",
|
|
dest="device_id",
|
|
default=False,
|
|
help="Only effect this device",
|
|
)
|
|
|
|
# Trigger Sound Alert
|
|
parser.add_argument(
|
|
"--sound",
|
|
action="store_true",
|
|
dest="sound",
|
|
default=False,
|
|
help="Play a sound on the device",
|
|
)
|
|
|
|
# Trigger Message w/Sound Alert
|
|
parser.add_argument(
|
|
"--message",
|
|
action="store",
|
|
dest="message",
|
|
default=False,
|
|
help="Optional Text Message to display with a sound",
|
|
)
|
|
|
|
# Trigger Message (without Sound) Alert
|
|
parser.add_argument(
|
|
"--silentmessage",
|
|
action="store",
|
|
dest="silentmessage",
|
|
default=False,
|
|
help="Optional Text Message to display with no sounds",
|
|
)
|
|
|
|
# Lost Mode
|
|
parser.add_argument(
|
|
"--lostmode",
|
|
action="store_true",
|
|
dest="lostmode",
|
|
default=False,
|
|
help="Enable Lost mode for the device",
|
|
)
|
|
parser.add_argument(
|
|
"--lostphone",
|
|
action="store",
|
|
dest="lost_phone",
|
|
default=False,
|
|
help="Phone Number allowed to call when lost mode is enabled",
|
|
)
|
|
parser.add_argument(
|
|
"--lostpassword",
|
|
action="store",
|
|
dest="lost_password",
|
|
default=False,
|
|
help="Forcibly active this passcode on the idevice",
|
|
)
|
|
parser.add_argument(
|
|
"--lostmessage",
|
|
action="store",
|
|
dest="lost_message",
|
|
default="",
|
|
help="Forcibly display this message when activating lost mode.",
|
|
)
|
|
|
|
# Output device data to an pickle file
|
|
parser.add_argument(
|
|
"--outputfile",
|
|
action="store_true",
|
|
dest="output_to_file",
|
|
default="",
|
|
help="Save device data to a file in the current directory.",
|
|
)
|
|
|
|
command_line = parser.parse_args(args)
|
|
|
|
username = command_line.username
|
|
password = command_line.password
|
|
|
|
if username and command_line.delete_from_keyring:
|
|
utils.delete_password_in_keyring(username)
|
|
|
|
failure_count = 0
|
|
while True:
|
|
# Which password we use is determined by your username, so we
|
|
# do need to check for this first and separately.
|
|
if not username:
|
|
parser.error('No username supplied')
|
|
|
|
if not password:
|
|
password = utils.get_password(
|
|
username,
|
|
interactive=command_line.interactive
|
|
)
|
|
|
|
if not password:
|
|
parser.error('No password supplied')
|
|
|
|
try:
|
|
api = pyicloud.PyiCloudService(
|
|
username.strip(),
|
|
password.strip()
|
|
)
|
|
if (
|
|
not utils.password_exists_in_keyring(username) and
|
|
command_line.interactive and
|
|
confirm("Save password in keyring? ")
|
|
):
|
|
utils.store_password_in_keyring(username, password)
|
|
break
|
|
except pyicloud.exceptions.PyiCloudFailedLoginException:
|
|
# If they have a stored password; we just used it and
|
|
# it did not work; let's delete it if there is one.
|
|
if utils.password_exists_in_keyring(username):
|
|
utils.delete_password_in_keyring(username)
|
|
|
|
message = "Bad username or password for {username}".format(
|
|
username=username,
|
|
)
|
|
password = None
|
|
|
|
failure_count += 1
|
|
if failure_count >= 3:
|
|
raise RuntimeError(message)
|
|
|
|
print(message, file=sys.stderr)
|
|
|
|
for dev in api.devices:
|
|
if (
|
|
not command_line.device_id or
|
|
(
|
|
command_line.device_id.strip().lower() ==
|
|
dev.content["id"].strip().lower()
|
|
)
|
|
):
|
|
# List device(s)
|
|
if command_line.locate:
|
|
dev.location()
|
|
|
|
if command_line.output_to_file:
|
|
create_pickled_data(
|
|
dev,
|
|
filename=(
|
|
dev.content["name"].strip().lower() + ".fmip_snapshot"
|
|
)
|
|
)
|
|
|
|
contents = dev.content
|
|
if command_line.longlist:
|
|
print("-"*30)
|
|
print(contents["name"])
|
|
for x in contents:
|
|
print("%20s - %s" % (x, contents[x]))
|
|
elif command_line.list:
|
|
print("-"*30)
|
|
print("Name - %s" % contents["name"])
|
|
print("Display Name - %s" % contents["deviceDisplayName"])
|
|
print("Location - %s" % contents["location"])
|
|
print("Battery Level - %s" % contents["batteryLevel"])
|
|
print("Battery Status- %s" % contents["batteryStatus"])
|
|
print("Device Class - %s" % contents["deviceClass"])
|
|
print("Device Model - %s" % contents["deviceModel"])
|
|
|
|
# Play a Sound on a device
|
|
if command_line.sound:
|
|
if command_line.device_id:
|
|
dev.play_sound()
|
|
else:
|
|
raise RuntimeError(
|
|
"\n\n\t\t%s %s\n\n" % (
|
|
"Sounds can only be played on a singular device.",
|
|
DEVICE_ERROR
|
|
)
|
|
)
|
|
|
|
# Display a Message on the device
|
|
if command_line.message:
|
|
if command_line.device_id:
|
|
dev.display_message(
|
|
subject='A Message',
|
|
message=command_line.message,
|
|
sounds=True
|
|
)
|
|
else:
|
|
raise RuntimeError(
|
|
"%s %s" % (
|
|
"Messages can only be played "
|
|
"on a singular device.",
|
|
DEVICE_ERROR
|
|
)
|
|
)
|
|
|
|
# Display a Silent Message on the device
|
|
if command_line.silentmessage:
|
|
if command_line.device_id:
|
|
dev.display_message(
|
|
subject='A Silent Message',
|
|
message=command_line.silentmessage,
|
|
sounds=False
|
|
)
|
|
else:
|
|
raise RuntimeError(
|
|
"%s %s" % (
|
|
"Silent Messages can only be played "
|
|
"on a singular device.",
|
|
DEVICE_ERROR
|
|
)
|
|
)
|
|
|
|
# Enable Lost mode
|
|
if command_line.lostmode:
|
|
if command_line.device_id:
|
|
dev.lost_device(
|
|
number=command_line.lost_phone.strip(),
|
|
text=command_line.lost_message.strip(),
|
|
newpasscode=command_line.lost_password.strip()
|
|
)
|
|
else:
|
|
raise RuntimeError(
|
|
"%s %s" % (
|
|
"Lost Mode can only be activated "
|
|
"on a singular device.",
|
|
DEVICE_ERROR
|
|
)
|
|
)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|