├── .firebaserc ├── .gitignore ├── .vscode └── .ropeproject │ ├── config.py │ └── objectdb ├── AliProduct.py ├── README.md ├── __pycache__ ├── AliProduct.cpython-36.pyc ├── common.cpython-36.pyc └── getProductInfo.cpython-36.pyc ├── aliProducts.txt ├── chromedriver.exe ├── common.py ├── cookies.pickle ├── database.rules.json ├── ebay.yaml ├── ebayManager.py ├── ebaysdk ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── config.cpython-36.pyc │ ├── connection.cpython-36.pyc │ ├── exception.cpython-36.pyc │ ├── response.cpython-36.pyc │ └── utils.cpython-36.pyc ├── config.py ├── connection.py ├── exception.py ├── finding │ └── __init__.py ├── http │ └── __init__.py ├── inventorymanagement │ └── __init__.py ├── merchandising │ └── __init__.py ├── parallel.py ├── policies │ └── __init__.py ├── poller │ ├── __init__.py │ └── orders.py ├── response.py ├── shopping │ ├── __init__.py │ └── __pycache__ │ │ └── __init__.cpython-36.pyc ├── soa │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ └── finditem.cpython-36.pyc │ └── finditem.py ├── trading │ ├── __init__.py │ └── __pycache__ │ │ └── __init__.cpython-36.pyc └── utils.py ├── findProducts.py ├── firebase.json ├── getProductInfo.py ├── itemTemplate.json ├── newCookies.pickle ├── proxyList.txt └── workingTemp.json /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "proxypool-f3996" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/.gitignore -------------------------------------------------------------------------------- /.vscode/.ropeproject/config.py: -------------------------------------------------------------------------------- 1 | # The default ``config.py`` 2 | # flake8: noqa 3 | 4 | 5 | def set_prefs(prefs): 6 | """This function is called before opening the project""" 7 | 8 | # Specify which files and folders to ignore in the project. 9 | # Changes to ignored resources are not added to the history and 10 | # VCSs. Also they are not returned in `Project.get_files()`. 11 | # Note that ``?`` and ``*`` match all characters but slashes. 12 | # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' 13 | # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' 14 | # '.svn': matches 'pkg/.svn' and all of its children 15 | # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' 16 | # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' 17 | prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', 18 | '.hg', '.svn', '_svn', '.git', '.tox'] 19 | 20 | # Specifies which files should be considered python files. It is 21 | # useful when you have scripts inside your project. Only files 22 | # ending with ``.py`` are considered to be python files by 23 | # default. 24 | # prefs['python_files'] = ['*.py'] 25 | 26 | # Custom source folders: By default rope searches the project 27 | # for finding source folders (folders that should be searched 28 | # for finding modules). You can add paths to that list. Note 29 | # that rope guesses project source folders correctly most of the 30 | # time; use this if you have any problems. 31 | # The folders should be relative to project root and use '/' for 32 | # separating folders regardless of the platform rope is running on. 33 | # 'src/my_source_folder' for instance. 34 | # prefs.add('source_folders', 'src') 35 | 36 | # You can extend python path for looking up modules 37 | # prefs.add('python_path', '~/python/') 38 | 39 | # Should rope save object information or not. 40 | prefs['save_objectdb'] = True 41 | prefs['compress_objectdb'] = False 42 | 43 | # If `True`, rope analyzes each module when it is being saved. 44 | prefs['automatic_soa'] = True 45 | # The depth of calls to follow in static object analysis 46 | prefs['soa_followed_calls'] = 0 47 | 48 | # If `False` when running modules or unit tests "dynamic object 49 | # analysis" is turned off. This makes them much faster. 50 | prefs['perform_doa'] = True 51 | 52 | # Rope can check the validity of its object DB when running. 53 | prefs['validate_objectdb'] = True 54 | 55 | # How many undos to hold? 56 | prefs['max_history_items'] = 32 57 | 58 | # Shows whether to save history across sessions. 59 | prefs['save_history'] = True 60 | prefs['compress_history'] = False 61 | 62 | # Set the number spaces used for indenting. According to 63 | # :PEP:`8`, it is best to use 4 spaces. Since most of rope's 64 | # unit-tests use 4 spaces it is more reliable, too. 65 | prefs['indent_size'] = 4 66 | 67 | # Builtin and c-extension modules that are allowed to be imported 68 | # and inspected by rope. 69 | prefs['extension_modules'] = [] 70 | 71 | # Add all standard c-extensions to extension_modules list. 72 | prefs['import_dynload_stdmods'] = True 73 | 74 | # If `True` modules with syntax errors are considered to be empty. 75 | # The default value is `False`; When `False` syntax errors raise 76 | # `rope.base.exceptions.ModuleSyntaxError` exception. 77 | prefs['ignore_syntax_errors'] = False 78 | 79 | # If `True`, rope ignores unresolvable imports. Otherwise, they 80 | # appear in the importing namespace. 81 | prefs['ignore_bad_imports'] = False 82 | 83 | # If `True`, rope will insert new module imports as 84 | # `from import ` by default. 85 | prefs['prefer_module_from_imports'] = False 86 | 87 | # If `True`, rope will transform a comma list of imports into 88 | # multiple separate import statements when organizing 89 | # imports. 90 | prefs['split_imports'] = False 91 | 92 | # If `True`, rope will remove all top-level import statements and 93 | # reinsert them at the top of the module when making changes. 94 | prefs['pull_imports_to_top'] = True 95 | 96 | # If `True`, rope will sort imports alphabetically by module name instead 97 | # of alphabetically by import statement, with from imports after normal 98 | # imports. 99 | prefs['sort_imports_alphabetically'] = False 100 | 101 | # Location of implementation of 102 | # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general 103 | # case, you don't have to change this value, unless you're an rope expert. 104 | # Change this value to inject you own implementations of interfaces 105 | # listed in module rope.base.oi.type_hinting.providers.interfaces 106 | # For example, you can add you own providers for Django Models, or disable 107 | # the search type-hinting in a class hierarchy, etc. 108 | prefs['type_hinting_factory'] = ( 109 | 'rope.base.oi.type_hinting.factory.default_type_hinting_factory') 110 | 111 | 112 | def project_opened(project): 113 | """This function is called after opening the project""" 114 | # Do whatever you like here! 115 | -------------------------------------------------------------------------------- /.vscode/.ropeproject/objectdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/.vscode/.ropeproject/objectdb -------------------------------------------------------------------------------- /AliProduct.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Jun 9, 2019 3 | 4 | @author: Bgray 5 | ''' 6 | class AliProduct: 7 | def __init__(self, productName, hasColors, images, imageDir, price, shippingPrice, shippingService, deliveryEst, maxDeliveryTimeDays, minDeliveryTimeDays, reviewScore, reviewNum, numOrders, itemSpecifics, colorOptions, preferredColor ): 8 | self.productName = productName 9 | self.hasColors = hasColors 10 | self.images = images 11 | self.imageDir = imageDir 12 | self.price = price 13 | self.shippingPrice = shippingPrice 14 | self.shippingService = shippingService 15 | self.deliveryEst = deliveryEst 16 | self.maxDeliveryTimeDays = maxDeliveryTimeDays 17 | self.minDeliveryTimeDays = minDeliveryTimeDays 18 | self.reviewScore = reviewScore 19 | self.numOrders = numOrders 20 | self.itemSpecifics = itemSpecifics 21 | self.colorOptions = colorOptions 22 | self.preferredColor = preferredColor 23 | 24 | def get_name(self): 25 | return self.name 26 | 27 | 28 | def get_images(self): 29 | return self.images 30 | 31 | 32 | def get_price(self): 33 | return self.price 34 | 35 | 36 | def get_shipping_price(self): 37 | return self.shippingPrice 38 | 39 | 40 | def get_shipping_service(self): 41 | return self.shippingService 42 | 43 | 44 | def get_review_score(self): 45 | return self.reviewScore 46 | 47 | 48 | def get_num_orders(self): 49 | return self.numOrders 50 | 51 | 52 | def set_name(self, value): 53 | self.name = value 54 | 55 | 56 | def set_images(self, value): 57 | self.images = value 58 | 59 | 60 | def set_price(self, value): 61 | self.price = value 62 | 63 | 64 | def set_shipping_price(self, value): 65 | self.shippingPrice = value 66 | 67 | 68 | def set_shipping_service(self, value): 69 | self.shippingService = value 70 | 71 | 72 | def set_review_score(self, value): 73 | self.reviewScore = value 74 | 75 | 76 | def set_num_orders(self, value): 77 | self.numOrders = value 78 | 79 | def printProduct(self): 80 | print(self.productName) 81 | print(self.hasColors) 82 | print(self.images) 83 | print(self.imageDir) 84 | print(self.price) 85 | print(self.shippingPrice) 86 | print(self.shippingService) 87 | print(self.deliveryEst) 88 | print(self.maxDeliveryTimeDays) 89 | print(self.minDeliveryTimeDays) 90 | print(self.reviewScore) 91 | print(self.numOrders) 92 | print(self.itemSpecifics) 93 | print(self.colorOptions) 94 | print(self.preferredColor) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DropShipper 2 | Automated drop shipper which totally automates the posting of items on ebay from Aliexpress 3 | -------------------------------------------------------------------------------- /__pycache__/AliProduct.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/__pycache__/AliProduct.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/common.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/__pycache__/common.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/getProductInfo.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/__pycache__/getProductInfo.cpython-36.pyc -------------------------------------------------------------------------------- /aliProducts.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/aliProducts.txt -------------------------------------------------------------------------------- /chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/chromedriver.exe -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | from selenium import * 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from selenium.webdriver.common.actions.interaction import KEY 5 | from selenium.webdriver.common.by import By 6 | from selenium.webdriver.common.keys import Keys 7 | from selenium.webdriver.support import expected_conditions as EC 8 | from selenium.webdriver.support.ui import WebDriverWait 9 | def waitAndClickCSS(css_selector, timeout, driver): 10 | try: 11 | element = WebDriverWait(driver, timeout).until( 12 | EC.presence_of_element_located((By.CSS_SELECTOR, css_selector)) 13 | ) 14 | finally: 15 | element.click() 16 | 17 | def waitAndClick(xPath, timeout, driver): 18 | try: 19 | element = WebDriverWait(driver, timeout).until( 20 | EC.presence_of_element_located((By.XPATH, xPath)) 21 | ) 22 | finally: 23 | element.click() 24 | 25 | def waitAndSendKeys(xPath, timeout, driver, keysToSend): 26 | try: 27 | element = WebDriverWait(driver, timeout).until( 28 | EC.presence_of_element_located((By.XPATH, xPath)) 29 | ) 30 | finally: 31 | element.send_keys(keysToSend) 32 | 33 | def waitAndSendKeysCSS(css_selector, timeout, driver, keysToSend): 34 | try: 35 | element = WebDriverWait(driver, timeout).until( 36 | EC.presence_of_element_located((By.CSS_SELECTOR, css_selector)) 37 | ) 38 | finally: 39 | element.send_keys(keysToSend) 40 | 41 | 42 | def waitAndTextCSS(css_selector, timeout, driver): 43 | try: 44 | element = WebDriverWait(driver, timeout).until( 45 | EC.presence_of_element_located((By.CSS_SELECTOR, css_selector)) 46 | ) 47 | finally: 48 | return element.text -------------------------------------------------------------------------------- /cookies.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/cookies.pickle -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": "auth == null", 4 | ".write": "auth == null" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ebay.yaml: -------------------------------------------------------------------------------- 1 | name: ebay_api_config 2 | 3 | # Trading API - https://www.x.com/developers/ebay/products/trading-api 4 | api.ebay.com: 5 | compatability: 719 6 | appid: ENTER_YOUR_APPID_HERE 7 | certid: ENTER_YOUR_CERTID_HERE 8 | devid: ENTER_YOUR_DEVID_HERE 9 | token: ENTER_YOUR_TOKEN_HERE 10 | -------------------------------------------------------------------------------- /ebayManager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 2012-2013 eBay Software Foundation 4 | Authored by: Tim Keefer 5 | Licensed under CDDL 1.0 6 | ''' 7 | from time import sleep 8 | import getProductInfo 9 | from AliProduct import AliProduct 10 | import pprint 11 | import json 12 | import os 13 | import requests 14 | import math 15 | import ebaysdk 16 | from ebaysdk.trading import Connection as Trading 17 | from ebaysdk.exception import ConnectionError 18 | 19 | api = Trading(appid='BradGray-Soldi-PRD-947f2a39f-550d6605', certid='PRD-47f2a39f61ac-fb65-4c10-b1a3-fafd', 20 | devid='2786f563-f132-4c58-a214-e6e93e174878', 21 | warnings=False) 22 | 23 | 24 | def getItemInfo(itemID): 25 | try: 26 | 27 | itemData = { 28 | "IncludeItemSpecifics": "true", 29 | "ItemID": itemID 30 | } 31 | 32 | response = api.execute('GetItem', itemData) 33 | validateResponse(response) 34 | return response.dict()['Item'] 35 | 36 | except ConnectionError as e: 37 | print(e) 38 | print(e.response.dict()) 39 | 40 | 41 | def validateResponse(response): 42 | assert(response.dict()['Ack'] == 'Success') 43 | 44 | 45 | def printResponse(response): 46 | pprint.pprint(response.dict()) 47 | 48 | 49 | def getItemFromProduct(product: AliProduct): 50 | 51 | item = {} 52 | with open("itemTemplate.json", "r") as f: 53 | item = json.load(f) 54 | if (product.hasColors): 55 | item['Item']['PictureDetails']['PictureURL'] = uploadImageToEbay(os.path.dirname(__file__) + "/" + product.preferredColor['thisColorImg'], product.imageDir) 56 | else: 57 | item['Item']['PictureDetails']['PictureURL'] = [] 58 | for image in product.images: 59 | item['Item']['PictureDetails']['PictureURL'].append(uploadImageToEbay(image, product.imageDir)) 60 | item['Item']['PrimaryCategory']['CategoryID'] = getSuggestedCategories(product.productName) 61 | item['Item']['Title'] = generateTitle(product.productName) 62 | item['Item']['Description'] = product.productName + '. Condition is New with tags.' 63 | item['Item']['BuyItNowPrice'] = 0.0 64 | item['Item']['ListingDetails']['MinimumBestOfferPrice'] = roundUpPrice(product.price * 3) 65 | item['Item']['ShippingDetails']['ShippingServiceOptions']['ShippingTimeMin'] = product.minDeliveryTimeDays 66 | item['Item']['ShippingDetails']['ShippingServiceOptions']['ShippingTimeMax'] = product.maxDeliveryTimeDays 67 | item['Item']['StartPrice'] = roundUpPrice(product.price * 4) 68 | item['Item']['ItemSpecifics']['NameValueList'] = product.itemSpecifics['NameValueList'] 69 | return item 70 | 71 | 72 | def generateTitle(rawTitle): 73 | t = 'NWT ' + rawTitle 74 | t_words = t.split() 75 | t_final = '' 76 | word_ctr = 0 77 | while len(t_words[word_ctr] + ' ' + t_final) < 70: 78 | t_final += ' ' 79 | t_final += t_words[word_ctr] 80 | word_ctr += 1 81 | return t_final 82 | 83 | 84 | def getSuggestedCategories(query): 85 | newCallData = { 86 | 'Query': query, 87 | } 88 | response = api.execute('GetSuggestedCategories', newCallData) 89 | rDict = response.dict() 90 | if (int(rDict['CategoryCount']) > 0): 91 | return rDict['SuggestedCategoryArray']['SuggestedCategory'][0]['Category']['CategoryID'] 92 | else: 93 | return '31387' 94 | 95 | 96 | 97 | def uploadImageToEbay(filePath, picName): 98 | 99 | try: 100 | files = {'file': ('EbayImage', open(filePath, 'rb'))} 101 | pictureData = { 102 | "PictureName": picName 103 | } 104 | response = api.execute('UploadSiteHostedPictures', pictureData, files=files) 105 | return response.dict()['SiteHostedPictureDetails']['FullURL'] 106 | 107 | except ConnectionError as e: 108 | print(e) 109 | print(e.response.dict()) 110 | 111 | def uploadPictureFromFilesystem(filepath): 112 | 113 | try: 114 | 115 | api = Trading(appid='BradGray-Soldi-PRD-947f2a39f-550d6605', certid='PRD-47f2a39f61ac-fb65-4c10-b1a3-fafd', 116 | devid='2786f563-f132-4c58-a214-e6e93e174878', 117 | warnings=False) 118 | # pass in an open file 119 | # the Requests module will close the file 120 | files = {'file': ('EbayImage', open(filepath, 'rb'))} 121 | 122 | pictureData = { 123 | "WarningLevel": "High", 124 | "PictureName": "WorldLeaders" 125 | } 126 | 127 | api.execute('UploadSiteHostedPictures', pictureData, files=files) 128 | 129 | except ConnectionError as e: 130 | print(e) 131 | print(e.response.dict()) 132 | 133 | 134 | 135 | def verifyAddItem(item): 136 | try: 137 | response = api.execute('VerifyAddItem', item) 138 | 139 | except ConnectionError as e: 140 | print(e) 141 | print(e.response.dict()) 142 | 143 | def addItem(item): 144 | try: 145 | response = api.execute('AddItem', item) 146 | except ConnectionError as e: 147 | print(e) 148 | print(e.response.dict()) 149 | 150 | 151 | def roundUpPrice(price): 152 | return math.ceil(price) - 0.01 153 | 154 | if __name__ == "__main__": 155 | with open('aliProducts.txt', 'r', encoding = "utf-16") as f: 156 | for line in f: 157 | # try: 158 | print("adding aliproduct %s" % line) 159 | p = getProductInfo.getAliProdInfo(line) 160 | addItem(getItemFromProduct(p)) 161 | sleep(120) 162 | # except Exception as e: 163 | # print("===============================================") 164 | # print("ERROR adding aliproduct: %s" % line) 165 | # print(e) 166 | # print("===============================================") 167 | # continue 168 | # p = getProductInfo.getAliProdInfo('https://www.aliexpress.com/item/32889675086.html?spm=2114.search0104.3.121.500667bd65JdeT&ws_ab_test=searchweb0_0%2Csearchweb201602_3_10065_10130_10068_10547_319_10546_317_10548_10545_10696_10084_453_454_10083_10618_10307_537_536_10059_10884_10887_321_322_10103%2Csearchweb201603_53%2CppcSwitch_0&algo_expid=f7517f6c-ad71-4b08-bb8a-a0b06501c97d-15&algo_pvid=f7517f6c-ad71-4b08-bb8a-a0b06501c97d') 169 | # #getItemFromProduct(p) 170 | # addItem(getItemFromProduct(p)) 171 | 172 | -------------------------------------------------------------------------------- /ebaysdk/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import platform 10 | import logging 11 | 12 | __version__ = '2.1.5' 13 | Version = __version__ # for backward compatibility 14 | 15 | try: 16 | from logging import NullHandler 17 | except ImportError: 18 | class NullHandler(logging.Handler): 19 | 20 | def emit(self, record): 21 | pass 22 | 23 | UserAgent = 'eBaySDK/%s Python/%s %s/%s' % ( 24 | __version__, 25 | platform.python_version(), 26 | platform.system(), 27 | platform.release() 28 | ) 29 | 30 | log = logging.getLogger('ebaysdk') 31 | 32 | if not log.handlers: 33 | log.addHandler(NullHandler()) 34 | 35 | 36 | def get_version(): 37 | return __version__ 38 | 39 | 40 | def set_stream_logger(level=logging.DEBUG, format_string=None): 41 | log.handlers = [] 42 | 43 | if not format_string: 44 | format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s" 45 | 46 | log.setLevel(level) 47 | fh = logging.StreamHandler() 48 | fh.setLevel(level) 49 | formatter = logging.Formatter(format_string) 50 | fh.setFormatter(formatter) 51 | log.addHandler(fh) 52 | 53 | 54 | def trading(*args, **kwargs): 55 | raise ImportError( 56 | 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 57 | 'from ebaysdk import trading', 58 | 'from ebaysdk.trading import Connection as trading', 59 | ) 60 | ) 61 | 62 | 63 | def shopping(*args, **kwargs): 64 | raise ImportError( 65 | 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 66 | 'from ebaysdk import shopping', 67 | 'from ebaysdk.shopping import Connection as shopping', 68 | ) 69 | ) 70 | 71 | 72 | def finding(*args, **kwargs): 73 | raise ImportError( 74 | 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 75 | 'from ebaysdk import finding', 76 | 'from ebaysdk.finding import Connection as finding', 77 | ) 78 | ) 79 | 80 | 81 | def merchandising(*args, **kwargs): 82 | raise ImportError( 83 | 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 84 | 'from ebaysdk import merchandising', 85 | 'from ebaysdk.merchandising import Connection as merchandising', 86 | ) 87 | ) 88 | 89 | 90 | def html(*args, **kwargs): 91 | raise ImportError( 92 | 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 93 | 'from ebaysdk import html', 94 | 'from ebaysdk.http import Connection as html', 95 | ) 96 | ) 97 | 98 | 99 | def parallel(*args, **kwargs): 100 | raise ImportError( 101 | 'SDK import must be changed as follows:\n\n- %s\n+ %s\n\n' % ( 102 | 'from ebaysdk import parallel', 103 | 'from ebaysdk.parallel import Parallel as parallel', 104 | ) 105 | ) 106 | -------------------------------------------------------------------------------- /ebaysdk/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/__pycache__/config.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/__pycache__/config.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/__pycache__/connection.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/__pycache__/connection.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/__pycache__/exception.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/__pycache__/exception.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/__pycache__/response.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/__pycache__/response.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/__pycache__/utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/__pycache__/utils.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import os 10 | 11 | from ebaysdk import log 12 | from ebaysdk.exception import ConnectionConfigError 13 | from ebaysdk.utils import parse_yaml 14 | 15 | 16 | class Config(object): 17 | """Config Class for all APIs connections 18 | 19 | >>> c = Config(domain='api.ebay.com') 20 | >>> c.set('fname', 'tim') 21 | >>> c.get('fname') 22 | 'tim' 23 | >>> c.get('missingkey', 'defaultvalue') 24 | 'defaultvalue' 25 | >>> c.set('number', 22) 26 | >>> c.get('number') 27 | 22 28 | """ 29 | 30 | def __init__(self, domain, connection_kwargs=dict(), config_file='ebay.yaml'): 31 | self.config_file = config_file 32 | self.domain = domain 33 | self.values = dict() 34 | self.config_file_used = [] 35 | self.connection_kwargs = connection_kwargs 36 | 37 | self._populate_yaml_defaults() 38 | 39 | def _populate_yaml_defaults(self): 40 | "Returns a dictionary of YAML defaults." 41 | 42 | # check for absolute path 43 | if self.config_file and os.path.exists(self.config_file): 44 | self.config_file_used = self.config_file 45 | 46 | dataobj = parse_yaml(self.config_file) 47 | 48 | for k, val in dataobj.get(self.domain, {}).items(): 49 | self.set(k, val) 50 | 51 | return self 52 | 53 | # check other directories 54 | dirs = ['.', os.path.expanduser('~'), '/etc'] 55 | for mydir in dirs: 56 | myfile = "%s/%s" % (mydir, self.config_file) 57 | 58 | if os.path.exists(myfile): 59 | self.config_file_used = myfile 60 | 61 | dataobj = parse_yaml(myfile) 62 | 63 | for k, val in dataobj.get(self.domain, {}).items(): 64 | self.set(k, val) 65 | 66 | return self 67 | 68 | if self.config_file: 69 | raise ConnectionConfigError( 70 | 'config file %s not found. Set config_file=None for use without YAML config.' % self.config_file) 71 | 72 | def file(self): 73 | return self.config_file_used 74 | 75 | def get(self, cKey, defaultValue=None): 76 | # log.debug('get: %s=%s' % (cKey, self.values.get(cKey, defaultValue))) 77 | return self.values.get(cKey, defaultValue) 78 | 79 | def set(self, cKey, defaultValue, force=False): 80 | 81 | if force: 82 | # log.debug('set (force): %s=%s' % (cKey, defaultValue)) 83 | self.values.update({cKey: defaultValue}) 84 | 85 | elif cKey in self.connection_kwargs and self.connection_kwargs[cKey] is not None: 86 | # log.debug('set: %s=%s' % (cKey, self.connection_kwargs[cKey])) 87 | self.values.update({cKey: self.connection_kwargs[cKey]}) 88 | 89 | # otherwise, use yaml default and then fall back to 90 | # the default set in the __init__() 91 | else: 92 | if cKey not in self.values: 93 | # log.debug('set: %s=%s' % (cKey, defaultValue)) 94 | self.values.update({cKey: defaultValue}) 95 | else: 96 | pass 97 | -------------------------------------------------------------------------------- /ebaysdk/connection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | from ebaysdk import log 10 | 11 | import re 12 | import time 13 | import uuid 14 | import webbrowser 15 | 16 | from requests import Request, Session 17 | from requests.adapters import HTTPAdapter 18 | 19 | from xml.dom.minidom import parseString 20 | from xml.parsers.expat import ExpatError 21 | 22 | from ebaysdk import set_stream_logger, UserAgent 23 | from ebaysdk.utils import getNodeText as getNodeTextUtils, smart_encode, smart_decode 24 | from ebaysdk.utils import getValue, smart_encode_request_data 25 | from ebaysdk.response import Response 26 | from ebaysdk.exception import ConnectionError, ConnectionResponseError 27 | 28 | HTTP_SSL = { 29 | False: 'http', 30 | True: 'https', 31 | } 32 | 33 | 34 | class BaseConnection(object): 35 | """Base Connection Class.""" 36 | 37 | def __init__(self, debug=False, method='GET', 38 | proxy_host=None, timeout=20, proxy_port=80, 39 | parallel=None, escape_xml=False, **kwargs): 40 | 41 | if debug: 42 | set_stream_logger() 43 | 44 | self.response = None 45 | self.request = None 46 | self.verb = None 47 | self.config = None 48 | self.debug = debug 49 | self.method = method 50 | self.timeout = timeout 51 | self.proxy_host = proxy_host 52 | self.proxy_port = proxy_port 53 | self.escape_xml = escape_xml 54 | self.datetime_nodes = [] 55 | self._list_nodes = [] 56 | 57 | self.proxies = dict() 58 | if self.proxy_host: 59 | proxy = 'http://%s:%s' % (self.proxy_host, self.proxy_port) 60 | self.proxies = { 61 | 'http': proxy, 62 | 'https': proxy 63 | } 64 | 65 | self.session = Session() 66 | self.session.mount('http://', HTTPAdapter(max_retries=3)) 67 | self.session.mount('https://', HTTPAdapter(max_retries=3)) 68 | 69 | self.parallel = parallel 70 | 71 | self.base_list_nodes = [] 72 | self.datetime_nodes = [] 73 | 74 | self._reset() 75 | 76 | def debug_callback(self, debug_type, debug_message): 77 | log.debug('type: ' + str(debug_type) + ' message' + str(debug_message)) 78 | 79 | def v(self, *args, **kwargs): 80 | return getValue(self.response.dict(), *args, **kwargs) 81 | 82 | def getNodeText(self, nodelist): 83 | return getNodeTextUtils(nodelist) 84 | 85 | def _reset(self): 86 | self.response = None 87 | self.request = None 88 | self.verb = None 89 | self._list_nodes = [] 90 | self._request_id = None 91 | self._request_dict = {} 92 | self._time = time.time() 93 | self._response_content = None 94 | self._response_dom = None 95 | self._response_obj = None 96 | self._response_soup = None 97 | self._response_dict = None 98 | self._response_error = None 99 | self._resp_body_errors = [] 100 | self._resp_body_warnings = [] 101 | self._resp_codes = [] 102 | 103 | def _add_prefix(self, nodes, verb): 104 | if verb: 105 | for i, v in enumerate(nodes): 106 | if not nodes[i].startswith(verb.lower()): 107 | nodes[i] = "%sresponse.%s" % ( 108 | verb.lower(), nodes[i].lower()) 109 | 110 | def execute(self, verb, data=None, list_nodes=[], verb_attrs=None, files=None): 111 | "Executes the HTTP request." 112 | log.debug('execute: verb=%s data=%s' % (verb, data)) 113 | 114 | self._reset() 115 | 116 | self._list_nodes += list_nodes 117 | self._add_prefix(self._list_nodes, verb) 118 | 119 | if hasattr(self, 'base_list_nodes'): 120 | self._list_nodes += self.base_list_nodes 121 | 122 | self.build_request(verb, data, verb_attrs, files) 123 | self.execute_request() 124 | 125 | if hasattr(self.response, 'content'): 126 | self.process_response() 127 | self.error_check() 128 | 129 | log.debug('total time=%s' % (time.time() - self._time)) 130 | 131 | return self.response 132 | 133 | def build_request(self, verb, data, verb_attrs, files=None): 134 | 135 | self.verb = verb 136 | self._request_dict = data 137 | self._request_id = uuid.uuid4() 138 | 139 | url = self.build_request_url(verb) 140 | 141 | headers = self.build_request_headers(verb) 142 | headers.update({'User-Agent': UserAgent, 143 | 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) 144 | 145 | # if we are adding files, we ensure there is no Content-Type header already defined 146 | # otherwise Request will use the existing one which is likely not to be multipart/form-data 147 | # data must also be a dict so we make it so if needed 148 | 149 | requestData = self.build_request_data(verb, data, verb_attrs) 150 | if files: 151 | del(headers['Content-Type']) 152 | if isinstance(requestData, str): # pylint: disable-msg=E0602 153 | requestData = {'XMLPayload': requestData} 154 | 155 | request = Request(self.method, 156 | url, 157 | data=smart_encode_request_data(requestData), 158 | headers=headers, 159 | files=files, 160 | ) 161 | 162 | self.request = request.prepare() 163 | 164 | def build_request_headers(self, verb): 165 | return {} 166 | 167 | def build_request_data(self, verb, data, verb_attrs): 168 | return "" 169 | 170 | def build_request_url(self, verb): 171 | url = "%s://%s%s" % ( 172 | HTTP_SSL[self.config.get('https', False)], 173 | self.config.get('domain'), 174 | self.config.get('uri') 175 | ) 176 | return url 177 | 178 | def execute_request(self): 179 | 180 | log.debug("REQUEST (%s): %s %s" 181 | % (self._request_id, self.request.method, self.request.url)) 182 | log.debug('headers=%s' % self.request.headers) 183 | log.debug('body=%s' % self.request.body) 184 | 185 | if self.parallel: 186 | self.parallel._add_request(self) 187 | return None 188 | 189 | self.response = self.session.send(self.request, 190 | verify=True, 191 | proxies=self.proxies, 192 | timeout=self.timeout, 193 | allow_redirects=True 194 | ) 195 | 196 | log.debug('RESPONSE (%s):' % self._request_id) 197 | log.debug('elapsed time=%s' % self.response.elapsed) 198 | log.debug('status code=%s' % self.response.status_code) 199 | log.debug('headers=%s' % self.response.headers) 200 | log.debug('content=%s' % self.response.text) 201 | 202 | def process_response(self, parse_response=True): 203 | """Post processing of the response""" 204 | 205 | self.response = Response(self.response, 206 | verb=self.verb, 207 | list_nodes=self._list_nodes, 208 | datetime_nodes=self.datetime_nodes, 209 | parse_response=parse_response) 210 | 211 | self.session.close() 212 | # set for backward compatibility 213 | self._response_content = self.response.content 214 | 215 | if self.response.status_code != 200: 216 | self._response_error = self.response.reason 217 | 218 | def error_check(self): 219 | estr = self.error() 220 | 221 | if estr and self.config.get('errors', True): 222 | log.error(estr) 223 | raise ConnectionError(estr, self.response) 224 | 225 | def response_codes(self): 226 | return self._resp_codes 227 | 228 | def response_status(self): 229 | "Retuns the HTTP response status string." 230 | 231 | return self.response.reason 232 | 233 | def response_code(self): 234 | "Returns the HTTP response status code." 235 | 236 | return self.response.status_code 237 | 238 | def response_content(self): 239 | return self.response.content 240 | 241 | def response_soup(self): 242 | "Returns a BeautifulSoup object of the response." 243 | 244 | if not self._response_soup: 245 | try: 246 | from bs4 import BeautifulStoneSoup 247 | except ImportError: 248 | from BeautifulSoup import BeautifulStoneSoup 249 | log.warn( 250 | 'DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') 251 | 252 | self._response_soup = BeautifulStoneSoup( 253 | smart_decode(self.response_content) 254 | ) 255 | 256 | return self._response_soup 257 | 258 | def response_obj(self): 259 | log.warn('response_obj() DEPRECATED, use response.reply instead') 260 | return self.response.reply 261 | 262 | def response_dom(self): 263 | """ Deprecated: use self.response.dom() instead 264 | Returns the response DOM (xml.dom.minidom). 265 | """ 266 | log.warn('response_dom() DEPRECATED, use response.dom instead') 267 | 268 | if not self._response_dom: 269 | dom = None 270 | content = None 271 | 272 | try: 273 | if self.response.content: 274 | regex = re.compile(b'xmlns="[^"]+"') 275 | content = regex.sub(b'', self.response.content) 276 | else: 277 | content = "<%sResponse>" % ( 278 | self.verb, self.verb) 279 | 280 | dom = parseString(content) 281 | self._response_dom = dom.getElementsByTagName( 282 | self.verb + 'Response')[0] 283 | 284 | except ExpatError as e: 285 | raise ConnectionResponseError( 286 | "Invalid Verb: %s (%s)" % (self.verb, e), self.response) 287 | except IndexError: 288 | self._response_dom = dom 289 | 290 | return self._response_dom 291 | 292 | def response_dict(self): 293 | "Returns the response dictionary." 294 | log.warn( 295 | 'response_dict() DEPRECATED, use response.dict() or response.reply instead') 296 | 297 | return self.response.reply 298 | 299 | def response_json(self): 300 | "Returns the response JSON." 301 | log.warn('response_json() DEPRECATED, use response.json() instead') 302 | 303 | return self.response.json() 304 | 305 | def _get_resp_body_errors(self): 306 | """Parses the response content to pull errors. 307 | 308 | Child classes should override this method based on what the errors in the 309 | XML response body look like. They can choose to look at the 'ack', 310 | 'Errors', 'errorMessage' or whatever other fields the service returns. 311 | the implementation below is the original code that was part of error() 312 | """ 313 | 314 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 315 | return self._resp_body_errors 316 | 317 | errors = [] 318 | 319 | if self.verb is None: 320 | return errors 321 | 322 | dom = self.response.dom() 323 | if dom is None: 324 | return errors 325 | 326 | return [] 327 | 328 | def error(self): 329 | "Builds and returns the api error message." 330 | 331 | error_array = [] 332 | if self._response_error: 333 | error_array.append(self._response_error) 334 | 335 | error_array.extend(self._get_resp_body_errors()) 336 | 337 | if len(error_array) > 0: 338 | # Force all errors to be unicode in a proper way 339 | error_array = [smart_decode(smart_encode(e)) for e in error_array] 340 | error_string = u"{verb}: {message}".format( 341 | verb=self.verb, message=u", ".join(error_array)) 342 | 343 | return error_string 344 | 345 | return None 346 | 347 | def opendoc(self): 348 | webbrowser.open(self.config.get('doc_url')) 349 | -------------------------------------------------------------------------------- /ebaysdk/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | 10 | class EbaySDKError(Exception): 11 | 12 | def __init__(self, msg, response=None): 13 | super(EbaySDKError, self).__init__(u'%s' % msg) 14 | self.message = u'%s' % msg 15 | self.response = response 16 | 17 | def __str__(self): 18 | return repr(self.message) 19 | 20 | 21 | class ConnectionError(EbaySDKError): 22 | pass 23 | 24 | 25 | class ConnectionConfigError(EbaySDKError): 26 | pass 27 | 28 | 29 | class ConnectionResponseError(EbaySDKError): 30 | pass 31 | 32 | 33 | class RequestPaginationError(EbaySDKError): 34 | pass 35 | 36 | 37 | class PaginationLimit(EbaySDKError): 38 | pass 39 | -------------------------------------------------------------------------------- /ebaysdk/finding/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import os 10 | 11 | from ebaysdk import log 12 | from ebaysdk.connection import BaseConnection 13 | from ebaysdk.exception import RequestPaginationError, PaginationLimit 14 | from ebaysdk.config import Config 15 | from ebaysdk.utils import dict2xml 16 | 17 | 18 | class Connection(BaseConnection): 19 | """Connection class for the Finding service 20 | 21 | API documentation: 22 | https://www.x.com/developers/ebay/products/finding-api 23 | 24 | Supported calls: 25 | findItemsAdvanced 26 | findItemsByCategory 27 | (all others, see API docs) 28 | 29 | Doctests: 30 | >>> f = Connection(config_file=os.environ.get('EBAY_YAML'), debug=False) 31 | >>> retval = f.execute('findItemsAdvanced', {'keywords': u'El Niño'}) 32 | >>> error = f.error() 33 | >>> print(error) 34 | None 35 | >>> if not f.error(): 36 | ... print(f.response.reply.itemSearchURL != '') 37 | ... items = f.response.reply.searchResult.item 38 | ... print(len(items) > 2) 39 | ... print(f.response.reply.ack) 40 | True 41 | True 42 | Success 43 | 44 | """ 45 | 46 | def __init__(self, **kwargs): 47 | """Finding class constructor. 48 | 49 | Keyword arguments: 50 | domain -- API endpoint (default: svcs.ebay.com) 51 | config_file -- YAML defaults (default: ebay.yaml) 52 | debug -- debugging enabled (default: False) 53 | warnings -- warnings enabled (default: False) 54 | uri -- API endpoint uri (default: /services/search/FindingService/v1) 55 | appid -- eBay application id 56 | siteid -- eBay country site id (default: EBAY-US) 57 | version -- version number (default: 1.0.0) 58 | https -- execute of https (default: False) 59 | proxy_host -- proxy hostname 60 | proxy_port -- proxy port number 61 | timeout -- HTTP request timeout (default: 20) 62 | parallel -- ebaysdk parallel object 63 | response_encoding -- API encoding (default: XML) 64 | request_encoding -- API encoding (default: XML) 65 | """ 66 | 67 | super(Connection, self).__init__(method='POST', **kwargs) 68 | 69 | self.config = Config(domain=kwargs.get('domain', 'svcs.ebay.com'), 70 | connection_kwargs=kwargs, 71 | config_file=kwargs.get('config_file', 'ebay.yaml')) 72 | 73 | # override yaml defaults with args sent to the constructor 74 | self.config.set('domain', kwargs.get('domain', 'svcs.ebay.com')) 75 | self.config.set('uri', '/services/search/FindingService/v1') 76 | self.config.set('https', False) 77 | self.config.set('warnings', True) 78 | self.config.set('errors', True) 79 | self.config.set('siteid', 'EBAY-US') 80 | self.config.set('response_encoding', 'XML') 81 | self.config.set('request_encoding', 'XML') 82 | self.config.set('proxy_host', None) 83 | self.config.set('proxy_port', None) 84 | self.config.set('token', None) 85 | self.config.set('iaf_token', None) 86 | self.config.set('appid', None) 87 | self.config.set('version', '1.12.0') 88 | self.config.set('service', 'FindingService') 89 | self.config.set( 90 | 'doc_url', 'http://developer.ebay.com/DevZone/finding/CallRef/index.html') 91 | 92 | self.datetime_nodes = ['starttimefrom', 'timestamp', 'starttime', 93 | 'endtime'] 94 | self.base_list_nodes = [ 95 | 'findcompleteditemsresponse.categoryhistogramcontainer.categoryhistogram', 96 | 'finditemsadvancedresponse.categoryhistogramcontainer.categoryhistogram', 97 | 'finditemsbycategoryresponse.categoryhistogramcontainer.categoryhistogram', 98 | 'finditemsbyimageresponse.categoryhistogramcontainer.categoryhistogram', 99 | 'finditemsbykeywordsresponse.categoryhistogramcontainer.categoryhistogram', 100 | 'finditemsbyproductresponse.categoryhistogramcontainer.categoryhistogram', 101 | 'finditemsinebaystoresresponse.categoryhistogramcontainer.categoryhistogram', 102 | 'finditemsinebaystoresresponse.categoryhistogramcontainer.categoryhistogram.childcategoryhistogram', 103 | 'findcompleteditemsresponse.aspecthistogramcontainer.aspect', 104 | 'finditemsadvancedresponse.aspecthistogramcontainer.aspect', 105 | 'finditemsbycategoryresponse.aspecthistogramcontainer.aspect', 106 | 'finditemsbyimageresponse.aspecthistogramcontainer.aspect', 107 | 'finditemsbykeywordsresponse.aspecthistogramcontainer.aspect', 108 | 'finditemsbyproductresponse.aspecthistogramcontainer.aspect', 109 | 'finditemsinebaystoresresponse.aspecthistogramcontainer.aspect', 110 | 'findcompleteditemsresponse.aspect.valuehistogram', 111 | 'finditemsadvancedresponse.aspect.valuehistogram', 112 | 'finditemsbycategoryresponse.aspect.valuehistogram', 113 | 'finditemsbyimageresponse.aspect.valuehistogram', 114 | 'finditemsbykeywordsresponse.aspect.valuehistogram', 115 | 'finditemsbyproductresponse.aspect.valuehistogram', 116 | 'finditemsinebaystoresresponse.aspect.valuehistogram', 117 | 'findcompleteditemsresponse.aspectfilter.aspectvaluename', 118 | 'finditemsadvancedresponse.aspectfilter.aspectvaluename', 119 | 'finditemsbycategoryresponse.aspectfilter.aspectvaluename', 120 | 'finditemsbyimageresponse.aspectfilter.aspectvaluename', 121 | 'finditemsbykeywordsresponse.aspectfilter.aspectvaluename', 122 | 'finditemsbyproductresponse.aspectfilter.aspectvaluename', 123 | 'finditemsinebaystoresresponse.aspectfilter.aspectvaluename', 124 | 'findcompleteditemsresponse.searchresult.item', 125 | 'finditemsadvancedresponse.searchresult.item', 126 | 'finditemsbycategoryresponse.searchresult.item', 127 | 'finditemsbyimageresponse.searchresult.item', 128 | 'finditemsbykeywordsresponse.searchresult.item', 129 | 'finditemsbyproductresponse.searchresult.item', 130 | 'finditemsinebaystoresresponse.searchresult.item', 131 | 'findcompleteditemsresponse.domainfilter.domainname', 132 | 'finditemsadvancedresponse.domainfilter.domainname', 133 | 'finditemsbycategoryresponse.domainfilter.domainname', 134 | 'finditemsbyimageresponse.domainfilter.domainname', 135 | 'finditemsbykeywordsresponse.domainfilter.domainname', 136 | 'finditemsinebaystoresresponse.domainfilter.domainname', 137 | 'findcompleteditemsresponse.itemfilter.value', 138 | 'finditemsadvancedresponse.itemfilter.value', 139 | 'finditemsbycategoryresponse.itemfilter.value', 140 | 'finditemsbyimageresponse.itemfilter.value', 141 | 'finditemsbykeywordsresponse.itemfilter.value', 142 | 'finditemsbyproductresponse.itemfilter.value', 143 | 'finditemsinebaystoresresponse.itemfilter.value', 144 | 'findcompleteditemsresponse.conditionhistogramcontainer.conditionhistogram', 145 | 'finditemsadvancedresponse.conditionhistogramcontainer.conditionhistogram', 146 | 'finditemsbycategoryresponse.conditionhistogramcontainer.conditionhistogram', 147 | 'finditemsbyimageresponse.conditionhistogramcontainer.conditionhistogram', 148 | 'finditemsbykeywordsresponse.conditionhistogramcontainer.conditionhistogram', 149 | 'finditemsinebaystoresresponse.conditionhistogramcontainer.conditionhistogram', 150 | 'finditemsbyproductresponse.conditionhistogramcontainer.conditionhistogram', 151 | 'findcompleteditemsresponse.searchitem.paymentmethod', 152 | 'finditemsadvancedresponse.searchitem.paymentmethod', 153 | 'finditemsbycategoryresponse.searchitem.paymentmethod', 154 | 'finditemsbyimageresponse.searchitem.paymentmethod', 155 | 'finditemsbykeywordsresponse.searchitem.paymentmethod', 156 | 'finditemsbyproductresponse.searchitem.paymentmethod', 157 | 'finditemsinebaystoresresponse.searchitem.paymentmethod', 158 | 'findcompleteditemsresponse.searchitem.gallerypluspictureurl', 159 | 'finditemsadvancedresponse.searchitem.gallerypluspictureurl', 160 | 'finditemsbycategoryresponse.searchitem.gallerypluspictureurl', 161 | 'finditemsbyimageresponse.searchitem.gallerypluspictureurl', 162 | 'finditemsbykeywordsresponse.searchitem.gallerypluspictureurl', 163 | 'finditemsbyproductresponse.searchitem.gallerypluspictureurl', 164 | 'finditemsinebaystoresresponse.searchitem.gallerypluspictureurl', 165 | 'finditemsbycategoryresponse.searchitem.attribute', 166 | 'finditemsadvancedresponse.searchitem.attribute', 167 | 'finditemsbykeywordsresponse.searchitem.attribute', 168 | 'finditemsinebaystoresresponse.searchitem.attribute', 169 | 'finditemsbyproductresponse.searchitem.attribute', 170 | 'findcompleteditemsresponse.searchitem.attribute', 171 | 'findcompleteditemsresponse.shippinginfo.shiptolocations', 172 | 'finditemsadvancedresponse.shippinginfo.shiptolocations', 173 | 'finditemsbycategoryresponse.shippinginfo.shiptolocations', 174 | 'finditemsbyimageresponse.shippinginfo.shiptolocations', 175 | 'finditemsbykeywordsresponse.shippinginfo.shiptolocations', 176 | 'finditemsbyproductresponse.shippinginfo.shiptolocations', 177 | 'finditemsinebaystoresresponse.shippinginfo.shiptolocations', 178 | ] 179 | 180 | def build_request_headers(self, verb): 181 | return { 182 | "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), 183 | "X-EBAY-SOA-SERVICE-VERSION": self.config.get('version', ''), 184 | "X-EBAY-SOA-SECURITY-APPNAME": self.config.get('appid', ''), 185 | "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), 186 | "X-EBAY-SOA-OPERATION-NAME": verb, 187 | "X-EBAY-SOA-REQUEST-DATA-FORMAT": self.config.get('request_encoding', ''), 188 | "X-EBAY-SOA-RESPONSE-DATA-FORMAT": self.config.get('response_encoding', ''), 189 | "Content-Type": "text/xml" 190 | } 191 | 192 | def build_request_data(self, verb, data, verb_attrs): 193 | xml = "" 194 | xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">" 195 | xml += dict2xml(data, self.escape_xml) 196 | xml += "" 197 | 198 | return xml 199 | 200 | def warnings(self): 201 | warning_string = "" 202 | 203 | if len(self._resp_body_warnings) > 0: 204 | warning_string = "%s: %s" \ 205 | % (self.verb, ", ".join(self._resp_body_warnings)) 206 | 207 | return warning_string 208 | 209 | def _get_resp_body_errors(self): 210 | """Parses the response content to pull errors. 211 | 212 | Child classes should override this method based on what the errors in the 213 | XML response body look like. They can choose to look at the 'ack', 214 | 'Errors', 'errorMessage' or whatever other fields the service returns. 215 | the implementation below is the original code that was part of error() 216 | """ 217 | 218 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 219 | return self._resp_body_errors 220 | 221 | errors = [] 222 | warnings = [] 223 | resp_codes = [] 224 | 225 | if self.verb is None: 226 | return errors 227 | 228 | dom = self.response.dom() 229 | if dom is None: 230 | return errors 231 | 232 | for e in dom.findall("error"): 233 | eSeverity = None 234 | eDomain = None 235 | eMsg = None 236 | eId = None 237 | 238 | try: 239 | eSeverity = e.findall('severity')[0].text 240 | except IndexError: 241 | pass 242 | 243 | try: 244 | eDomain = e.findall('domain')[0].text 245 | except IndexError: 246 | pass 247 | 248 | try: 249 | eId = e.findall('errorId')[0].text 250 | if int(eId) not in resp_codes: 251 | resp_codes.append(int(eId)) 252 | except IndexError: 253 | pass 254 | 255 | try: 256 | eMsg = e.findall('message')[0].text 257 | except IndexError: 258 | pass 259 | 260 | msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ 261 | % (eDomain, eSeverity, eId, eMsg) 262 | 263 | if eSeverity == 'Warning': 264 | warnings.append(msg) 265 | else: 266 | errors.append(msg) 267 | 268 | self._resp_body_warnings = warnings 269 | self._resp_body_errors = errors 270 | self._resp_codes = resp_codes 271 | 272 | if self.config.get('warnings') and len(warnings) > 0: 273 | log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) 274 | 275 | try: 276 | if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): 277 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 278 | 279 | elif len(errors) > 0: 280 | if self.config.get('errors'): 281 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 282 | 283 | return errors 284 | except AttributeError as e: 285 | return errors 286 | 287 | return [] 288 | 289 | def next_page(self): 290 | if type(self._request_dict) is not dict: 291 | raise RequestPaginationError( 292 | "request data is not of type dict", self.response) 293 | 294 | epp = self._request_dict.get( 295 | 'paginationInput', {}).get('enteriesPerPage', None) 296 | num = int(self.response.reply.paginationOutput.pageNumber) 297 | 298 | if num >= int(self.response.reply.paginationOutput.totalPages): 299 | raise PaginationLimit("no more pages to process", self.response) 300 | return None 301 | 302 | self._request_dict['paginationInput'] = {} 303 | 304 | if epp: 305 | self._request_dict['paginationInput']['enteriesPerPage'] = epp 306 | 307 | self._request_dict['paginationInput']['pageNumber'] = int(num) + 1 308 | 309 | return self.execute(self.verb, self._request_dict) 310 | -------------------------------------------------------------------------------- /ebaysdk/http/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import uuid 10 | import time 11 | 12 | from xml.parsers.expat import ExpatError 13 | from xml.dom.minidom import parseString 14 | try: 15 | from urllib.parse import urlencode 16 | except ImportError: 17 | from urllib import urlencode 18 | 19 | from requests import Request 20 | 21 | from ebaysdk import log, UserAgent 22 | from ebaysdk.connection import BaseConnection 23 | from ebaysdk.exception import ConnectionResponseError 24 | from ebaysdk.config import Config 25 | from ebaysdk.utils import getNodeText 26 | from ebaysdk.response import Response 27 | 28 | 29 | class Connection(BaseConnection): 30 | """HTML class for traditional calls. 31 | 32 | Doctests: 33 | >>> h = Connection() 34 | >>> retval = h.execute('http://feeds.feedburner.com/slashdot/audio?format=xml') 35 | >>> print(h.response.reply.rss.channel.ttl) 36 | 2 37 | >>> title = h.response.dom().xpath('//title')[0] 38 | >>> print(title.text) 39 | Slashdot 40 | >>> print(h.error()) 41 | None 42 | >>> h = Connection(method='POST', debug=False) 43 | >>> retval = h.execute('http://www.ebay.com/') 44 | >>> print(h.response.content != '') 45 | True 46 | >>> print(h.response_code()) 47 | 200 48 | >>> h.response.reply 49 | {} 50 | """ 51 | 52 | def __init__(self, method='GET', **kwargs): 53 | """HTML class constructor. 54 | 55 | Keyword arguments: 56 | debug -- debugging enabled (default: False) 57 | method -- GET/POST/PUT (default: GET) 58 | proxy_host -- proxy hostname 59 | proxy_port -- proxy port number 60 | timeout -- HTTP request timeout (default: 20) 61 | parallel -- ebaysdk parallel object 62 | """ 63 | 64 | super(Connection, self).__init__(method=method, **kwargs) 65 | 66 | self.config = Config(domain=None, 67 | connection_kwargs=kwargs, 68 | config_file=kwargs.get('config_file', 'ebay.yaml')) 69 | 70 | def response_dom(self): 71 | "Returns the HTTP response dom." 72 | 73 | try: 74 | if not self._response_dom: 75 | self._response_dom = parseString(self.response.content) 76 | 77 | return self._response_dom 78 | except ExpatError: 79 | raise ConnectionResponseError( 80 | 'response is not well-formed', self.response) 81 | 82 | def response_dict(self): 83 | "Return the HTTP response dictionary." 84 | 85 | try: 86 | 87 | return self.response.dict() 88 | 89 | except ExpatError: 90 | raise ConnectionResponseError( 91 | 'response is not well-formed', self.response) 92 | 93 | def execute(self, url, data=None, headers=dict(), method=None, parse_response=True): 94 | "Executes the HTTP request." 95 | log.debug('execute: url=%s data=%s' % (url, data)) 96 | 97 | if method: 98 | self.method = method 99 | 100 | self._reset() 101 | self.build_request(url, data, headers) 102 | self.execute_request() 103 | 104 | if self.parallel: 105 | self.parallel._add_request(self) 106 | return None 107 | 108 | self.process_response(parse_response=parse_response) 109 | self.error_check() 110 | 111 | log.debug('total time=%s' % (time.time() - self._time)) 112 | 113 | return self.response 114 | 115 | def build_request(self, url, data, headers): 116 | 117 | self._request_id = uuid.uuid4() 118 | 119 | headers.update({'User-Agent': UserAgent, 120 | 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) 121 | 122 | kw = dict() 123 | if self.method == 'POST': 124 | kw['data'] = data 125 | else: 126 | kw['params'] = data 127 | 128 | request = Request(self.method, 129 | url, 130 | headers=headers, 131 | **kw 132 | ) 133 | 134 | self.request = request.prepare() 135 | 136 | def warnings(self): 137 | return '' 138 | -------------------------------------------------------------------------------- /ebaysdk/inventorymanagement/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Authored by: Michal Hernas 5 | Licensed under CDDL 1.0 6 | ''' 7 | 8 | import os 9 | 10 | from ebaysdk import log 11 | from ebaysdk.connection import BaseConnection 12 | from ebaysdk.exception import ConnectionError 13 | from ebaysdk.config import Config 14 | from ebaysdk.utils import dict2xml, smart_encode 15 | 16 | 17 | class Connection(BaseConnection): 18 | """Connection class for the Inventory Management service 19 | 20 | API documentation: 21 | http://developer.ebay.com/Devzone/store-pickup/InventoryManagement/index.html 22 | 23 | Supported calls: 24 | AddInventory 25 | AddInventoryLocation 26 | DeleteInventory 27 | DeleteInventoryLocation 28 | (all others, see API docs) 29 | 30 | Doctests: 31 | Create location first 32 | >>> f = Connection(config_file=os.environ.get('EBAY_YAML'), debug=False) 33 | 34 | Take care here, unicode string is put here specially to ensure lib can handle it properly. If not we got an error: 35 | UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 28: ordinal not in range(128) 36 | >>> try: 37 | ... retval = f.execute(u'AddInventoryLocation', { 38 | ... 'Address1': u'Alexanderplatz 12 ąśćł', 39 | ... 'Address2': u'Gebaude 6', 40 | ... 'City': u'Berlin', 41 | ... 'Country': u'DE', 42 | ... 'PostalCode': u'13355', 43 | ... 'Latitude': u'37.374488', 44 | ... 'Longitude': u'-122.032876', 45 | ... 'LocationID': u'ebaysdk_test', 46 | ... 'LocationType': u'STORE', 47 | ... 'Phone': u'(408)408-4080', 48 | ... 'URL': u'http://store.com', 49 | ... 'UTCOffset': u'+02:00', 50 | ... 'Name': 'Test', 51 | ... 'Region': 'Berlin', 52 | ... 'PickupInstructions': 'Pick it up soon', 53 | ... 'Hours': [{'Day': {'DayOfWeek': 1, 'Interval': {'Open': '08:00:00', 'Close': '10:00:00'}}}] 54 | ... }) 55 | ... print(f.response.reply.LocationID.lower()) 56 | ... except ConnectionError as e: 57 | ... print(f.error()) # doctest: +SKIP 58 | ebaysdk_test 59 | 60 | And now add item it it 61 | >>> try: 62 | ... f = Connection(config_file=os.environ.get('EBAY_YAML'), debug=False) 63 | ... retval = f.execute('AddInventory', {"SKU": "SKU_TEST", "Locations": {"Location": [ 64 | ... {"Availability": "IN_STOCK", "LocationID": "ebaysdk_test", "Quantity": 10} 65 | ... ]}}) 66 | ... print(f.response.reply.SKU.lower()) 67 | ... except ConnectionError as e: 68 | ... print(f.error()) # doctest: +SKIP 69 | sku_test 70 | 71 | 72 | Delete item from all locations 73 | >>> try: 74 | ... f = Connection(config_file=os.environ.get('EBAY_YAML'), debug=False) 75 | ... retval = f.execute('DeleteInventory', {"SKU": "SKU_TEST", "Confirm": 'true'}) 76 | ... print(f.response.reply.SKU.lower()) 77 | ... except ConnectionError as e: 78 | ... print(f.error()) # doctest: +SKIP 79 | sku_test 80 | 81 | 82 | Delete location 83 | >>> try: 84 | ... f = Connection(config_file=os.environ.get('EBAY_YAML'), debug=False) 85 | ... retval = f.execute('DeleteInventoryLocation', {"LocationID": "ebaysdk_test"}) 86 | ... print(f.response.reply.LocationID.lower()) 87 | ... except ConnectionError as e: 88 | ... print(f.error()) # doctest: +SKIP 89 | ebaysdk_test 90 | 91 | 92 | Check errors handling 93 | >>> try: 94 | ... f = Connection(token='WRONG TOKEN', config_file=os.environ.get('EBAY_YAML'), debug=False, errors=True) 95 | ... retval = f.execute('DeleteInventoryLocation', {"LocationID": "ebaysdk_test"}) 96 | ... except ConnectionError as e: 97 | ... print(f.error()) # doctest: +SKIP 98 | DeleteInventoryLocation: Bad Request, Class: RequestError, Severity: Error, Code: 503, Authentication: Invalid user token Authentication: Invalid user token 99 | 100 | 101 | Sometimes ebay returns us really weird error message, already reported to ebay, if it will be fixed I will remove 102 | all special cases to handle it. 103 | Example of wrong response: 104 | 105 | 106 | 107 | 108 | Wed May 06 2015 02:15:49 GMT-0700 (GMT-07:00) 109 | Failure 110 | 111 | Gateway Error 112 | Failover endpoint : Selling_Inventory_REST_SVC_V1 - no ready child endpoints 113 | 99.99 114 | Error 115 | RequestError 116 | 117 | null 118 | 653 119 | 120 | 121 | 122 | """ 123 | 124 | def __init__(self, **kwargs): 125 | """Inventory Management class constructor. 126 | 127 | Keyword arguments: 128 | domain -- API endpoint (default: api.ebay.com) 129 | config_file -- YAML defaults (default: ebay.yaml) 130 | debug -- debugging enabled (default: False) 131 | warnings -- warnings enabled (default: False) 132 | uri -- API endpoint uri (default: /selling/inventory/v1) 133 | token -- eBay application/user token 134 | version -- version number (default: 1.0.0) 135 | https -- execute of https (required by this API) (default: True) 136 | proxy_host -- proxy hostname 137 | proxy_port -- proxy port number 138 | timeout -- HTTP request timeout (default: 20) 139 | parallel -- ebaysdk parallel object 140 | response_encoding -- API encoding (default: XML) 141 | request_encoding -- API encoding (default: XML) 142 | """ 143 | 144 | super(Connection, self).__init__(method='POST', **kwargs) 145 | 146 | self.config = Config(domain=kwargs.get('domain', 'api.ebay.com'), 147 | connection_kwargs=kwargs, 148 | config_file=kwargs.get('config_file', 'ebay.yaml')) 149 | 150 | # override yaml defaults with args sent to the constructor 151 | self.config.set('domain', kwargs.get('domain', 'api.ebay.com')) 152 | self.config.set('uri', '/selling/inventory/v1') 153 | self.config.set('https', True) 154 | self.config.set('warnings', True) 155 | self.config.set('errors', True) 156 | self.config.set('siteid', None) 157 | self.config.set('response_encoding', 'XML') 158 | self.config.set('request_encoding', 'XML') 159 | self.config.set('proxy_host', None) 160 | self.config.set('proxy_port', None) 161 | self.config.set('token', None) 162 | self.config.set('iaf_token', None) 163 | self.config.set('appid', None) 164 | self.config.set('version', '1.0.0') 165 | self.config.set('service', 'InventoryManagement') 166 | self.config.set( 167 | 'doc_url', 'http://developer.ebay.com/Devzone/store-pickup/InventoryManagement/index.html') 168 | 169 | self.datetime_nodes = ['starttimefrom', 'timestamp', 'starttime', 170 | 'endtime'] 171 | self.base_list_nodes = [ 172 | ] 173 | 174 | endpoints = { 175 | 'addinventorylocation': 'locations/delta/add', 176 | 'addinventory': 'inventory/delta/add', 177 | 'deleteinventory': 'inventory/delta/delete', 178 | 'deleteinventorylocation': 'locations/delta/delete', 179 | } 180 | 181 | def build_request_url(self, verb): 182 | url = super(Connection, self).build_request_url(verb) 183 | endpoint = self.endpoints[verb.lower()] 184 | return "{0}/{1}".format(url, endpoint) 185 | 186 | def build_request_headers(self, verb): 187 | return { 188 | "Authorization": "TOKEN {0}".format(self.config.get('token')), 189 | "Content-Type": "application/xml" 190 | } 191 | 192 | def build_request_data(self, verb, data, verb_attrs): 193 | xml = "" 194 | xml += "<{verb}Request>".format(verb=verb) 195 | xml += dict2xml(data, self.escape_xml) 196 | xml += "".format(verb=verb) 197 | 198 | return xml 199 | 200 | def warnings(self): 201 | warning_string = "" 202 | 203 | if len(self._resp_body_warnings) > 0: 204 | warning_string = "{verb}: {message}" \ 205 | .format(verb=self.verb, message=", ".join(self._resp_body_warnings)) 206 | 207 | return warning_string 208 | 209 | def _get_resp_body_errors(self): 210 | """Parses the response content to pull errors. 211 | 212 | Child classes should override this method based on what the errors in the 213 | XML response body look like. They can choose to look at the 'ack', 214 | 'Errors', 'errorMessage' or whatever other fields the service returns. 215 | the implementation below is the original code that was part of error() 216 | """ 217 | 218 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 219 | return self._resp_body_errors 220 | 221 | errors = [] 222 | warnings = [] 223 | resp_codes = [] 224 | 225 | if self.verb is None: 226 | return errors 227 | 228 | dom = self.response.dom() 229 | 230 | if dom is None: 231 | return errors 232 | 233 | # In special case we get errors in this format... 234 | if not dom.findall('Errors') and dom.find('Body') is not None: 235 | dom = dom.find('Body').find('Response') 236 | 237 | if dom is None: 238 | return errors 239 | 240 | for e in dom.findall('Errors'): 241 | eSeverity = None 242 | eClass = None 243 | eShortMsg = None 244 | eLongMsg = None 245 | eCode = None 246 | 247 | try: 248 | eSeverity = e.findall('SeverityCode')[0].text 249 | except IndexError: 250 | pass 251 | 252 | try: 253 | eClass = e.findall('ErrorClassification')[0].text 254 | except IndexError: 255 | pass 256 | 257 | try: 258 | eCode = e.findall('ErrorCode')[0].text 259 | except IndexError: 260 | pass 261 | 262 | try: 263 | eShortMsg = e.findall('ShortMessage')[0].text 264 | except IndexError: 265 | pass 266 | 267 | try: 268 | eLongMsg = e.findall('LongMessage')[0].text 269 | except IndexError: 270 | pass 271 | 272 | try: 273 | eCode = e.findall('ErrorCode')[0].text 274 | try: 275 | int_code = int(eCode) 276 | except ValueError: 277 | int_code = None 278 | 279 | if int_code and int_code not in resp_codes: 280 | resp_codes.append(int_code) 281 | 282 | except IndexError: 283 | pass 284 | 285 | msg = "Class: {eClass}, Severity: {severity}, Code: {code}, {shortMsg} {longMsg}" \ 286 | .format(eClass=eClass, severity=eSeverity, code=eCode, shortMsg=smart_encode(eShortMsg), 287 | longMsg=smart_encode(eLongMsg)) 288 | 289 | if eSeverity == 'Warning': 290 | warnings.append(msg) 291 | else: 292 | errors.append(msg) 293 | 294 | self._resp_body_warnings = warnings 295 | self._resp_body_errors = errors 296 | self._resp_codes = resp_codes 297 | 298 | if self.config.get('warnings') and len(warnings) > 0: 299 | log.warn("{verb}: {message}\n\n".format( 300 | verb=self.verb, message="\n".join(warnings))) 301 | 302 | # In special case of error 500 on ebay side, we get really weird 303 | # response so I need to fallback to this one 304 | Ack = getattr(self.response.reply, 'Ack', None) 305 | if Ack is None: 306 | Ack = self.response.reply.Envelope.Body.Response.Ack 307 | 308 | if Ack == 'Failure': 309 | if self.config.get('errors'): 310 | log.error("{verb}: {message}\n\n".format( 311 | verb=self.verb, message="\n".join(errors))) 312 | 313 | return errors 314 | 315 | return [] 316 | -------------------------------------------------------------------------------- /ebaysdk/merchandising/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import os 10 | 11 | from ebaysdk.finding import Connection as FindingConnection 12 | from ebaysdk.utils import dict2xml 13 | 14 | 15 | class Connection(FindingConnection): 16 | """Connection class for the Merchandising service 17 | 18 | API documentation: 19 | http://developer.ebay.com/products/merchandising/ 20 | 21 | Supported calls: 22 | getMostWatchedItems 23 | getSimilarItems 24 | getTopSellingProducts 25 | (all others, see API docs) 26 | 27 | Doctests: 28 | >>> s = Connection(config_file=os.environ.get('EBAY_YAML')) 29 | >>> retval = s.execute('getMostWatchedItems', {'maxResults': 3}) 30 | >>> print(s.response.reply.ack) 31 | Success 32 | >>> print(s.error()) 33 | None 34 | """ 35 | 36 | def __init__(self, **kwargs): 37 | """Merchandising class constructor. 38 | 39 | Keyword arguments: 40 | domain -- API endpoint (default: open.api.ebay.com) 41 | config_file -- YAML defaults (default: ebay.yaml) 42 | debug -- debugging enabled (default: False) 43 | warnings -- warnings enabled (default: False) 44 | uri -- API endpoint uri (default: /MerchandisingService) 45 | appid -- eBay application id 46 | siteid -- eBay country site id (default: 0 (US)) 47 | version -- version number (default: 799) 48 | https -- execute of https (default: True) 49 | proxy_host -- proxy hostname 50 | proxy_port -- proxy port number 51 | timeout -- HTTP request timeout (default: 20) 52 | parallel -- ebaysdk parallel object 53 | response_encoding -- API encoding (default: XML) 54 | request_encoding -- API encoding (default: XML) 55 | """ 56 | 57 | super(Connection, self).__init__(**kwargs) 58 | 59 | self.config.set('uri', '/MerchandisingService', force=True) 60 | self.config.set('service', 'MerchandisingService', force=True) 61 | self.config.set( 62 | 'doc_url', 'http://developer.ebay.com/Devzone/merchandising/docs/CallRef/index.html') 63 | 64 | self.datetime_nodes = ['endtimeto', 'endtimefrom', 'timestamp'] 65 | self.base_list_nodes = [ 66 | 'getdealsresponse.itemrecommendations.item', 67 | 'getmostwatcheditemsresponse.itemrecommendations.item', 68 | 'getrelatedcategoryitemsresponse.itemrecommendations.item', 69 | 'getsimilaritemsresponse.itemrecommendations.item', 70 | 'gettopsellingproductsresponse.productrecommendations.product', 71 | 'getrelatedcategoryitemsresponse.itemfilter.value', 72 | 'getsimilaritemsresponse.itemfilter.value', 73 | ] 74 | 75 | def build_request_headers(self, verb): 76 | return { 77 | "X-EBAY-API-VERSION": self.config.get('version', ''), 78 | "EBAY-SOA-CONSUMER-ID": self.config.get('appid', ''), 79 | "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), 80 | "X-EBAY-SOA-OPERATION-NAME": verb, 81 | "X-EBAY-SOA-REQUEST-DATA-FORMAT": "XML", 82 | "X-EBAY-API-REQUEST-ENCODING": "XML", 83 | "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), 84 | "Content-Type": "text/xml" 85 | } 86 | 87 | def build_request_data(self, verb, data, verb_attrs): 88 | xml = "" 89 | xml += "<" + verb + "Request xmlns=\"http://www.ebay.com/marketplace/services\">" 90 | xml += dict2xml(data, self.escape_xml) 91 | xml += "" 92 | 93 | return xml 94 | -------------------------------------------------------------------------------- /ebaysdk/parallel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | import sys 9 | from ebaysdk.exception import ConnectionError 10 | 11 | # pylint: disable=import-error 12 | import grequests 13 | # pylint: enable=import-error 14 | 15 | if sys.version_info[0] >= 3: 16 | raise ImportError('grequests does not work with python3+') 17 | 18 | 19 | class Parallel(object): 20 | """ 21 | >>> from ebaysdk.finding import Connection as finding 22 | >>> from ebaysdk.shopping import Connection as shopping 23 | >>> from ebaysdk.http import Connection as html 24 | >>> import os 25 | >>> p = Parallel() 26 | >>> r1 = html(parallel=p) 27 | >>> retval = r1.execute('http://feeds.feedburner.com/slashdot/audio?format=xml') 28 | >>> r2 = finding(parallel=p, config_file=os.environ.get('EBAY_YAML')) 29 | >>> retval = r2.execute('findItemsAdvanced', {'keywords': 'shoes'}) 30 | >>> r3 = shopping(parallel=p, config_file=os.environ.get('EBAY_YAML')) 31 | >>> retval = r3.execute('FindItemsAdvanced', {'CharityID': 3897}) 32 | >>> p.wait() 33 | >>> print(p.error()) 34 | None 35 | >>> print(r1.response.reply.rss.channel.ttl) 36 | 2 37 | >>> print(r2.response.dict()['ack']) 38 | Success 39 | >>> print(r3.response.reply.Ack) 40 | Success 41 | """ 42 | 43 | def __init__(self): 44 | self._grequests = [] 45 | self._requests = [] 46 | self._errors = [] 47 | 48 | def _add_request(self, request): 49 | self._requests.append(request) 50 | 51 | def wait(self, timeout=20): 52 | "wait for all of the api requests to complete" 53 | 54 | self._errors = [] 55 | self._grequests = [] 56 | 57 | try: 58 | for r in self._requests: 59 | req = grequests.request(r.request.method, 60 | r.request.url, 61 | data=r.request.body, 62 | headers=r.request.headers, 63 | verify=False, 64 | proxies=r.proxies, 65 | timeout=r.timeout, 66 | allow_redirects=True) 67 | 68 | self._grequests.append(req) 69 | 70 | def exception_handler(request, exception): 71 | self._errors.append("%s" % exception) 72 | 73 | gresponses = grequests.map( 74 | self._grequests, exception_handler=exception_handler) 75 | 76 | for idx, r in enumerate(self._requests): 77 | r.response = gresponses[idx] 78 | r.process_response() 79 | r.error_check() 80 | 81 | if r.error(): 82 | self._errors.append(r.error()) 83 | 84 | except ConnectionError as e: 85 | self._errors.append("%s" % e) 86 | 87 | self._requests = [] 88 | 89 | def error(self): 90 | "builds and returns the api error message" 91 | 92 | if len(self._errors) > 0: 93 | return "parallel error:\n%s\n" % ("\n".join(self._errors)) 94 | 95 | return None 96 | 97 | 98 | if __name__ == '__main__': 99 | 100 | import doctest 101 | import sys 102 | 103 | failure_count, test_count = doctest.testmod() 104 | sys.exit(failure_count) 105 | -------------------------------------------------------------------------------- /ebaysdk/policies/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | from ebaysdk import log 10 | from ebaysdk.connection import BaseConnection 11 | from ebaysdk.config import Config 12 | from ebaysdk.utils import dict2xml 13 | 14 | 15 | class Connection(BaseConnection): 16 | """Connection class for the Business Policies service 17 | 18 | API documentation: 19 | http://developer.ebay.com/Devzone/business-policies 20 | 21 | Supported calls: 22 | addSellerProfile 23 | getSellerProfiles 24 | (all others, see API docs) 25 | 26 | """ 27 | 28 | def __init__(self, **kwargs): 29 | """Finding class constructor. 30 | 31 | Keyword arguments: 32 | domain -- API endpoint (default: svcs.ebay.com) 33 | config_file -- YAML defaults (default: ebay.yaml) 34 | debug -- debugging enabled (default: False) 35 | warnings -- warnings enabled (default: False) 36 | uri -- API endpoint uri (default: /services/selling/v1/SellerProfilesManagementService) 37 | appid -- eBay application id 38 | siteid -- eBay country site id (default: EBAY-US) 39 | version -- version number (default: 1.0.0) 40 | https -- execute of https (default: False) 41 | proxy_host -- proxy hostname 42 | proxy_port -- proxy port number 43 | timeout -- HTTP request timeout (default: 20) 44 | parallel -- ebaysdk parallel object 45 | response_encoding -- API encoding (default: XML) 46 | request_encoding -- API encoding (default: XML) 47 | """ 48 | 49 | super(Connection, self).__init__(method='POST', **kwargs) 50 | 51 | self.config = Config(domain=kwargs.get('domain', 'svcs.ebay.com'), 52 | connection_kwargs=kwargs, 53 | config_file=kwargs.get('config_file', 'ebay.yaml')) 54 | 55 | # override yaml defaults with args sent to the constructor 56 | self.config.set('domain', kwargs.get('domain', 'svcs.ebay.com')) 57 | self.config.set( 58 | 'uri', '/services/selling/v1/SellerProfilesManagementService') 59 | self.config.set('https', True) 60 | self.config.set('warnings', True) 61 | self.config.set('errors', True) 62 | self.config.set('siteid', 'EBAY-US') 63 | self.config.set('response_encoding', 'XML') 64 | self.config.set('request_encoding', 'XML') 65 | self.config.set('proxy_host', None) 66 | self.config.set('proxy_port', None) 67 | self.config.set('token', None) 68 | self.config.set('iaf_token', None) 69 | self.config.set('appid', None) 70 | self.config.set('version', '1.0.0') 71 | self.config.set('service', 'SellerProfilesManagementService') 72 | self.config.set( 73 | 'doc_url', 'http://developer.ebay.com/Devzone/business-policies/CallRef/index.html') 74 | 75 | self.datetime_nodes = ['deleteddate', 'timestamp', 'maxdeliverydate', 76 | 'mindeliverydate'] 77 | self.base_list_nodes = [ 78 | 'setsellerprofileresponse.paymentprofile.categorygroups.categorygroup', 79 | 'addsellerprofileresponse.paymentprofile.categorygroups.categorygroup', 80 | 'getsellerprofilesresponse.paymentprofilelist.paymentprofile.categorygroups.categorygroup', 81 | 'addsellerprofileresponse.returnpolicyprofile.categorygroups.categorygroup', 82 | 'setsellerprofileresponse.returnpolicyprofile.categorygroups.categorygroup', 83 | 'getsellerprofilesresponse.returnpolicyprofilelist.returnpolicyprofile.categorygroups.categorygroup', 84 | 'addsellerprofileresponse.shippingpolicyprofile.categorygroups.categorygroup', 85 | 'setsellerprofileresponse.shippingpolicyprofile.categorygroups.categorygroup', 86 | 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.categorygroups.categorygroup', 87 | 'consolidateshippingprofilesresponse.consolidationjob', 88 | 'getconsolidationjobstatusresponse.consolidationjob', 89 | 'addsellerprofileresponse.paymentprofile.paymentinfo.depositdetails', 90 | 'setsellerprofileresponse.paymentprofile.paymentinfo.depositdetails', 91 | 'getsellerprofilesresponse.paymentprofilelist.paymentprofile.paymentinfo.depositdetails', 92 | 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.freightshipping', 93 | 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.freightshipping', 94 | 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.freightshipping', 95 | 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.insurance', 96 | 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.insurance', 97 | 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.insurance', 98 | 'addsellerprofileresponse.paymentprofile.paymentinfo', 99 | 'setsellerprofileresponse.paymentprofile.paymentinfo', 100 | 'getsellerprofilesresponse.paymentprofilelist.paymentprofile.paymentinfo', 101 | 'addsellerprofileresponse.returnpolicyprofile.returnpolicyinfo', 102 | 'setsellerprofileresponse.returnpolicyprofile.returnpolicyinfo', 103 | 'getsellerprofilesresponse.returnpolicyprofilelist.returnpolicyprofile.returnpolicyinfo', 104 | 'addsellerprofileresponse.sellerprofile', 105 | 'setsellerprofileresponse.sellerprofile', 106 | 'getsellerprofilesresponse.paymentprofilelist.sellerprofile', 107 | 'getsellerprofilesresponse.returnpolicyprofilelist.sellerprofile', 108 | 'getsellerprofilesresponse.shippingpolicyprofilelist.sellerprofile', 109 | 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingpolicyinfoservice', 110 | 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingpolicyinfoservice', 111 | 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.shippingpolicyinfoservice', 112 | 'addsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingprofilediscountinfo', 113 | 'setsellerprofileresponse.shippingpolicyprofile.shippingpolicyinfo.shippingprofilediscountinfo', 114 | 'getsellerprofilesresponse.shippingpolicyprofilelist.shippingpolicyprofile.shippingpolicyinfo.shippingprofilediscountinfo' 115 | ] 116 | 117 | def build_request_headers(self, verb): 118 | return { 119 | "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), 120 | "X-EBAY-SOA-SERVICE-VERSION": self.config.get('version', ''), 121 | "X-EBAY-SOA-SECURITY-TOKEN": self.config.get('token', ''), 122 | "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), 123 | "X-EBAY-SOA-OPERATION-NAME": verb, 124 | "X-EBAY-SOA-REQUEST-DATA-FORMAT": self.config.get('request_encoding', ''), 125 | "X-EBAY-SOA-RESPONSE-DATA-FORMAT": self.config.get('response_encoding', ''), 126 | "Content-Type": "text/xml" 127 | } 128 | 129 | def build_request_data(self, verb, data, verb_attrs): 130 | xml = "" 131 | xml += "<{verb}Request xmlns=\"http://www.ebay.com/marketplace/selling/v1/services\">".format( 132 | verb=verb) 133 | xml += dict2xml(data, self.escape_xml) 134 | xml += "".format(verb=verb) 135 | 136 | return xml 137 | 138 | def warnings(self): 139 | warning_string = "" 140 | 141 | if len(self._resp_body_warnings) > 0: 142 | warning_string = "%s: %s" \ 143 | % (self.verb, ", ".join(self._resp_body_warnings)) 144 | 145 | return warning_string 146 | 147 | def _get_resp_body_errors(self): 148 | """Parses the response content to pull errors. 149 | 150 | Child classes should override this method based on what the errors in the 151 | XML response body look like. They can choose to look at the 'ack', 152 | 'Errors', 'errorMessage' or whatever other fields the service returns. 153 | the implementation below is the original code that was part of error() 154 | """ 155 | 156 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 157 | return self._resp_body_errors 158 | 159 | errors = [] 160 | warnings = [] 161 | resp_codes = [] 162 | 163 | if self.verb is None: 164 | return errors 165 | 166 | dom = self.response.dom() 167 | if dom is None: 168 | return errors 169 | 170 | for e in dom.findall("error"): 171 | eSeverity = None 172 | eDomain = None 173 | eMsg = None 174 | eId = None 175 | 176 | try: 177 | eSeverity = e.findall('severity')[0].text 178 | except IndexError: 179 | pass 180 | 181 | try: 182 | eDomain = e.findall('domain')[0].text 183 | except IndexError: 184 | pass 185 | 186 | try: 187 | eId = e.findall('errorId')[0].text 188 | if int(eId) not in resp_codes: 189 | resp_codes.append(int(eId)) 190 | except IndexError: 191 | pass 192 | 193 | try: 194 | eMsg = e.findall('message')[0].text 195 | except IndexError: 196 | pass 197 | 198 | msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ 199 | % (eDomain, eSeverity, eId, eMsg) 200 | 201 | if eSeverity == 'Warning': 202 | warnings.append(msg) 203 | else: 204 | errors.append(msg) 205 | 206 | self._resp_body_warnings = warnings 207 | self._resp_body_errors = errors 208 | self._resp_codes = resp_codes 209 | 210 | if self.config.get('warnings') and len(warnings) > 0: 211 | log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) 212 | 213 | try: 214 | if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): 215 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 216 | 217 | elif len(errors) > 0: 218 | if self.config.get('errors'): 219 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 220 | 221 | return errors 222 | except AttributeError as e: 223 | return errors 224 | 225 | return [] 226 | -------------------------------------------------------------------------------- /ebaysdk/poller/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | © 2012-2015 eBay Software Foundation 4 | Authored by: Tim Keefer 5 | Licensed under CDDL 1.0 6 | ''' 7 | import os 8 | import sys 9 | import logging 10 | 11 | from contextlib import contextmanager 12 | from optparse import OptionParser 13 | from ebaysdk import log, set_stream_logger 14 | 15 | 16 | @contextmanager 17 | def file_lock(lock_file): 18 | if os.path.exists(lock_file): 19 | log.info("skipping run, lock file found (%s)" % lock_file) 20 | sys.exit(-1) 21 | else: 22 | open(lock_file, 'w').write("1") 23 | try: 24 | yield 25 | finally: 26 | os.remove(lock_file) 27 | 28 | 29 | def parse_args(usage): 30 | 31 | parser = OptionParser(usage=usage) 32 | 33 | parser.add_option("-d", "--debug", 34 | action="store_true", dest="debug", default=False, 35 | help="Enabled debugging [default: %default]") 36 | parser.add_option("-H", "--hours", 37 | dest="hours", default=12, type='int', 38 | help="Specifies the number of hours [default: %default]") 39 | parser.add_option("-M", "--minutes", 40 | dest="minutes", default=0, type='int', 41 | help="Specifies the number of minutes [default: %default]") 42 | parser.add_option("-y", "--yaml", 43 | dest="yaml", default='ebay.yaml', 44 | help="Specifies the name of the YAML defaults file. [default: %default]") 45 | parser.add_option("-a", "--appid", 46 | dest="appid", default=None, 47 | help="Specifies the eBay application id to use.") 48 | parser.add_option("-p", "--devid", 49 | dest="devid", default=None, 50 | help="Specifies the eBay developer id to use.") 51 | parser.add_option("-c", "--certid", 52 | dest="certid", default=None, 53 | help="Specifies the eBay cert id to use.") 54 | parser.add_option("-s", "--siteid", 55 | dest="siteid", default=None, 56 | help="Specifies the eBay site id to use.") 57 | parser.add_option("-o", "--OrderRole", 58 | dest="OrderRole", default='Buyer', 59 | help="Specifies which OrderRole value to use. [default: %default]") 60 | parser.add_option("-O", "--OrderStatus", 61 | dest="OrderStatus", default='All', 62 | help="Specifies which OrderStatus value to use. [default: %default]") 63 | 64 | (opts, args) = parser.parse_args() 65 | 66 | if opts.debug: 67 | set_stream_logger(level=logging.DEBUG) 68 | 69 | return opts, args 70 | -------------------------------------------------------------------------------- /ebaysdk/poller/orders.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Authored by: Tim Keefer 5 | Licensed under CDDL 1.0 6 | ''' 7 | 8 | from datetime import datetime, timedelta 9 | 10 | from ebaysdk.trading import Connection as Trading 11 | from ebaysdk.poller import parse_args, file_lock 12 | from ebaysdk import log 13 | 14 | 15 | class Storage(object): 16 | 17 | def set(self, order): 18 | data = [ 19 | ("ID", order.OrderID), 20 | ("Status", order.OrderStatus), 21 | ("Seller Email", order.SellerEmail), 22 | ("Title", order.TransactionArray.Transaction[0].Item.Title), 23 | ("ItemID", order.TransactionArray.Transaction[0].Item.ItemID), 24 | ("QTY", order.TransactionArray.Transaction[0].QuantityPurchased), 25 | ("Payment Method", order.CheckoutStatus.PaymentMethod), 26 | ("Payment Date", getattr(order, 'PaidTime', 'Not Paid')), 27 | ("Total", (order.Total._currencyID + ' ' + order.Total.value)) 28 | ] 29 | 30 | if order.TransactionArray.Transaction[0].get('Variation', None): 31 | data.append( 32 | ("SKU", order.TransactionArray.Transaction[0].Variation.SKU)), 33 | 34 | data.extend([ 35 | ("Shipped Time", getattr(order, 'ShippedTime', 'Not Shipped')), 36 | ("Shipping Service", getattr(order, 'ShippingServiceSelected', 'N/A')) 37 | ]) 38 | 39 | if order.ShippingDetails.get('ShipmentTrackingDetails', None): 40 | data.extend([ 41 | ("Min Shipping Days", 42 | order.ShippingDetails.ShippingServiceOptions.ShippingTimeMin), 43 | ("Max Shipping Days", 44 | order.ShippingDetails.ShippingServiceOptions.ShippingTimeMax), 45 | ("Tracking", order.ShippingDetails.ShipmentTrackingDetails.ShipmentTrackingNumber), 46 | ("Carrier", order.ShippingDetails.ShipmentTrackingDetails.ShippingCarrierUsed), 47 | ("Cost", (order.ShippingDetails.ShippingServiceOptions.ShippingServiceCost._currencyID, 48 | order.ShippingDetails.ShippingServiceOptions.ShippingServiceCost.value)) 49 | ]) 50 | 51 | values_array = map((lambda x: "%s=%s" % (x[0], x[1])), data) 52 | log.debug(", ".join(values_array)) 53 | 54 | 55 | class Poller(object): 56 | 57 | def __init__(self, opts, storage=None): 58 | self.opts = opts 59 | self.storage = storage 60 | 61 | def run(self): 62 | 63 | with file_lock("/tmp/.ebaysdk-poller-orders.lock"): 64 | log.debug("Started poller %s" % __file__) 65 | 66 | to_time = datetime.utcnow() # - timedelta(days=4) 67 | 68 | from_time = to_time - timedelta(hours=self.opts.hours, 69 | minutes=self.opts.minutes) 70 | 71 | ebay_api = Trading(debug=self.opts.debug, config_file=self.opts.yaml, 72 | appid=self.opts.appid, certid=self.opts.certid, 73 | devid=self.opts.devid, siteid=self.opts.siteid, 74 | warnings=False) 75 | 76 | ebay_api.build_request('GetOrders', { 77 | 'DetailLevel': 'ReturnAll', 78 | 'OrderRole': self.opts.OrderRole, 79 | 'OrderStatus': self.opts.OrderStatus, 80 | 'Pagination': { 81 | 'EntriesPerPage': 25, 82 | 'PageNumber': 1, 83 | }, 84 | 'ModTimeFrom': from_time.strftime('%Y-%m-%dT%H:%M:%S.000Z'), 85 | 'ModTimeTo': to_time.strftime('%Y-%m-%dT%H:%M:%S.000Z'), 86 | }, None) 87 | 88 | for resp in ebay_api.pages(): 89 | 90 | if resp.reply.OrderArray: 91 | 92 | for order in resp.reply.OrderArray.Order: 93 | if self.storage: 94 | self.storage.set(order) 95 | else: 96 | log.debug("storage object not defined") 97 | else: 98 | log.debug("no orders to process") 99 | 100 | 101 | if __name__ == '__main__': 102 | (opts, args) = parse_args( 103 | "usage: python -m ebaysdk.poller.orders [options]") 104 | 105 | poller = Poller(opts, Storage()) 106 | poller.run() 107 | -------------------------------------------------------------------------------- /ebaysdk/response.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | import sys 9 | import lxml 10 | import copy 11 | import datetime 12 | from lxml.etree import XMLSyntaxError # pylint: disable-msg=E0611 13 | 14 | from collections import defaultdict 15 | import json 16 | 17 | from ebaysdk.utils import get_dom_tree, python_2_unicode_compatible 18 | from ebaysdk import log 19 | 20 | 21 | @python_2_unicode_compatible 22 | class ResponseDataObject(object): 23 | 24 | def __init__(self, mydict, datetime_nodes=[]): 25 | self._load_dict(mydict, list(datetime_nodes)) 26 | 27 | def __repr__(self): 28 | return str(self) 29 | 30 | def __str__(self): 31 | return "%s" % self.__dict__ 32 | 33 | def has_key(self, name): 34 | try: 35 | getattr(self, name) 36 | return True 37 | except AttributeError: 38 | return False 39 | 40 | def get(self, name, default=None): 41 | try: 42 | return getattr(self, name) 43 | except AttributeError: 44 | return default 45 | 46 | def _setattr(self, name, value, datetime_nodes): 47 | if name.lower() in datetime_nodes: 48 | try: 49 | ts = "%s %s" % (value.partition( 50 | 'T')[0], value.partition('T')[2].partition('.')[0]) 51 | value = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S') 52 | except ValueError: 53 | pass 54 | 55 | setattr(self, name, value) 56 | 57 | def _load_dict(self, mydict, datetime_nodes): 58 | if sys.version_info[0] >= 3: 59 | datatype = bytes 60 | else: 61 | datatype = unicode # pylint: disable-msg=E0602 62 | 63 | for a in mydict.items(): 64 | 65 | if isinstance(a[1], dict): 66 | o = ResponseDataObject(a[1], datetime_nodes) 67 | setattr(self, a[0], o) 68 | 69 | elif isinstance(a[1], list): 70 | objs = [] 71 | for i in a[1]: 72 | if i is None or isinstance(i, str) or isinstance(i, datatype): 73 | objs.append(i) 74 | else: 75 | objs.append(ResponseDataObject(i, datetime_nodes)) 76 | 77 | setattr(self, a[0], objs) 78 | else: 79 | self._setattr(a[0], a[1], datetime_nodes) 80 | 81 | 82 | class Response(object): 83 | ''' 84 | 85 | 86 | Success 87 | 1.12.0 88 | 2014-02-07T23:31:13.941Z 89 | 90 | 91 | 92 | 93 | 94 | 1 95 | 2 96 | 90 97 | 179 98 | 99 | http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1 100 | 101 | 102 | Doctests: 103 | >>> xml = b'Success1.12.02014-02-07T23:31:13.941ZItem Two1190179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' 104 | >>> o = ResponseDataObject({'content': xml}, []) 105 | >>> r = Response(o, verb='findItemsByProduct', list_nodes=['finditemsbyproductresponse.searchresult.item', 'finditemsbyproductresponse.paginationoutput.pagenumber']) 106 | >>> len(r.dom().getchildren()) > 2 107 | True 108 | >>> r.reply.searchResult._count == '1' 109 | True 110 | >>> type(r.reply.searchResult.item)==list 111 | True 112 | >>> len(r.reply.paginationOutput.pageNumber) == 1 113 | True 114 | >>> xml = b'Success1.12.02014-02-07T23:31:13.941ZItem TwoUSMXItem One1290179http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1' 115 | >>> o = ResponseDataObject({'content': xml}, []) 116 | >>> r = Response(o, verb='findItemsByProduct', list_nodes=['searchResult.item']) 117 | >>> len(r.dom().getchildren()) > 2 118 | True 119 | >>> import json 120 | >>> j = json.loads(r.json(), encoding='utf8') 121 | >>> json.dumps(j, sort_keys=True) 122 | '{"ack": "Success", "itemSearchURL": "http://www.ebay.com/ctg/53039031?_ddo=1&_ipg=2&_pgn=1", "paginationOutput": {"entriesPerPage": "2", "pageNumber": "1", "totalEntries": "179", "totalPages": "90"}, "searchResult": {"_count": "2", "item": [{"name": "Item Two", "shipping": {"c": ["US", "MX"]}}, {"name": "Item One"}]}, "timestamp": "2014-02-07T23:31:13.941Z", "version": "1.12.0"}' 123 | >>> sorted(r.dict().keys()) 124 | ['ack', 'itemSearchURL', 'paginationOutput', 'searchResult', 'timestamp', 'version'] 125 | >>> len(r.reply.searchResult.item) == 2 126 | True 127 | >>> r.reply.searchResult._count == '2' 128 | True 129 | >>> item = r.reply.searchResult.item[0] 130 | >>> item.name == 'Item Two' 131 | True 132 | >>> len(item.shipping.c) == 2 133 | True 134 | ''' 135 | 136 | def __init__(self, obj, verb=None, list_nodes=[], datetime_nodes=[], parse_response=True): 137 | self._list_nodes = copy.copy(list_nodes) 138 | self._obj = obj 139 | 140 | if parse_response: 141 | try: 142 | self._dom = self._parse_xml(obj.content) 143 | self._dict = self._etree_to_dict(self._dom) 144 | 145 | if verb and 'Envelope' in self._dict.keys(): 146 | elem = self._dom.find('Body').find('%sResponse' % verb) 147 | if elem is not None: 148 | self._dom = elem 149 | 150 | self._dict = self._dict['Envelope'][ 151 | 'Body'].get('%sResponse' % verb, self._dict) 152 | elif verb: 153 | elem = self._dom.find('%sResponse' % verb) 154 | if elem is not None: 155 | self._dom = elem 156 | 157 | self._dict = self._dict.get( 158 | '%sResponse' % verb, self._dict) 159 | 160 | self.reply = ResponseDataObject(self._dict, 161 | datetime_nodes=copy.copy(datetime_nodes)) 162 | except XMLSyntaxError as e: 163 | log.debug('response parse failed: %s' % e) 164 | self.reply = ResponseDataObject({}, []) 165 | else: 166 | self.reply = ResponseDataObject({}, []) 167 | 168 | def _get_node_path(self, t): 169 | i = t 170 | path = [] 171 | path.insert(0, i.tag) 172 | while 1: 173 | try: 174 | path.insert(0, i.getparent().tag) 175 | i = i.getparent() 176 | except AttributeError: 177 | break 178 | 179 | return '.'.join(path) 180 | 181 | @staticmethod 182 | def _pullval(v): 183 | if len(v) == 1: 184 | return v[0] 185 | else: 186 | return v 187 | 188 | def _etree_to_dict(self, t): 189 | if type(t) == lxml.etree._Comment: # pylint: disable=no-member 190 | return {} 191 | 192 | # remove xmlns from nodes, I find them meaningless 193 | t.tag = self._get_node_tag(t) 194 | 195 | d = {t.tag: {} if t.attrib else None} 196 | children = list(t) 197 | if children: 198 | dd = defaultdict(list) 199 | for dc in map(self._etree_to_dict, children): 200 | for k, v in dc.items(): 201 | dd[k].append(v) 202 | 203 | d = {t.tag: dict((k, self._pullval(v)) for k, v in dd.items())} 204 | # d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}} 205 | 206 | # TODO: Optimizations? Forces a node to type list 207 | parent_path = self._get_node_path(t) 208 | for k in d[t.tag].keys(): 209 | path = "%s.%s" % (parent_path, k) 210 | if path.lower() in self._list_nodes: 211 | if not isinstance(d[t.tag][k], list): 212 | d[t.tag][k] = [d[t.tag][k]] 213 | 214 | if t.attrib: 215 | d[t.tag].update(('_' + k, v) for k, v in t.attrib.items()) 216 | if t.text: 217 | text = t.text.strip() 218 | if children or t.attrib: 219 | if text: 220 | d[t.tag]['value'] = text 221 | else: 222 | d[t.tag] = text 223 | return d 224 | 225 | def __getattr__(self, name): 226 | return getattr(self._obj, name) 227 | 228 | def _parse_xml(self, xml): 229 | return get_dom_tree(xml) 230 | 231 | def _get_node_tag(self, node): 232 | return node.tag.replace('{' + node.nsmap.get(node.prefix, '') + '}', '') 233 | 234 | def dom(self, lxml=True): 235 | if not lxml: 236 | # create and return a cElementTree DOM 237 | pass 238 | 239 | return self._dom 240 | 241 | def dict(self): 242 | return self._dict 243 | 244 | def json(self): 245 | return json.dumps(self.dict()) 246 | 247 | 248 | if __name__ == '__main__': 249 | 250 | import os 251 | import sys 252 | 253 | sys.path.insert(0, '%s/' % os.path.dirname(__file__)) 254 | 255 | import doctest 256 | failure_count, test_count = doctest.testmod() 257 | sys.exit(failure_count) 258 | -------------------------------------------------------------------------------- /ebaysdk/shopping/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import os 10 | 11 | from ebaysdk import log 12 | from ebaysdk.connection import BaseConnection 13 | from ebaysdk.config import Config 14 | from ebaysdk.utils import getNodeText, dict2xml 15 | 16 | 17 | class Connection(BaseConnection): 18 | """Shopping API class 19 | 20 | API documentation: 21 | http://developer.ebay.com/products/shopping/ 22 | 23 | Supported calls: 24 | getSingleItem 25 | getMultipleItems 26 | (all others, see API docs) 27 | 28 | Doctests: 29 | >>> s = Connection(config_file=os.environ.get('EBAY_YAML')) 30 | >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) 31 | >>> print(s.response_obj().Ack) 32 | Success 33 | >>> print(s.error()) 34 | None 35 | """ 36 | 37 | def __init__(self, **kwargs): 38 | """Shopping class constructor. 39 | 40 | Keyword arguments: 41 | domain -- API endpoint (default: open.api.ebay.com) 42 | config_file -- YAML defaults (default: ebay.yaml) 43 | debug -- debugging enabled (default: False) 44 | warnings -- warnings enabled (default: True) 45 | errors -- errors enabled (default: True) 46 | uri -- API endpoint uri (default: /shopping) 47 | appid -- eBay application id 48 | siteid -- eBay country site id (default: 0 (US)) 49 | version -- version number (default: 799) 50 | https -- execute of https (default: True) 51 | proxy_host -- proxy hostname 52 | proxy_port -- proxy port number 53 | timeout -- HTTP request timeout (default: 20) 54 | parallel -- ebaysdk parallel object 55 | trackingid -- ID to identify you to your tracking partner 56 | trackingpartnercode -- third party who is your tracking partner 57 | response_encoding -- API encoding (default: XML) 58 | request_encoding -- API encoding (default: XML) 59 | 60 | More affiliate tracking info: 61 | http://developer.ebay.com/DevZone/shopping/docs/Concepts/ShoppingAPI_FormatOverview.html#StandardURLParameters 62 | 63 | """ 64 | super(Connection, self).__init__(method='POST', **kwargs) 65 | 66 | self.config = Config(domain=kwargs.get('domain', 'open.api.ebay.com'), 67 | connection_kwargs=kwargs, 68 | config_file=kwargs.get('config_file', 'ebay.yaml')) 69 | 70 | # override yaml defaults with args sent to the constructor 71 | self.config.set('domain', kwargs.get('domain', 'open.api.ebay.com')) 72 | self.config.set('uri', '/shopping') 73 | self.config.set('warnings', True) 74 | self.config.set('errors', True) 75 | self.config.set('https', False) 76 | self.config.set('siteid', '0') 77 | self.config.set('response_encoding', 'XML') 78 | self.config.set('request_encoding', 'XML') 79 | self.config.set('proxy_host', None) 80 | self.config.set('proxy_port', None) 81 | self.config.set('appid', None) 82 | self.config.set('version', '799') 83 | self.config.set('trackingid', None) 84 | self.config.set('trackingpartnercode', None) 85 | self.config.set( 86 | 'doc_url', 'http://developer.ebay.com/DevZone/Shopping/docs/CallRef/index.html') 87 | 88 | if self.config.get('https') and self.debug: 89 | print("HTTPS is not supported on the Shopping API.") 90 | 91 | self.datetime_nodes = ['timestamp', 'registrationdate', 'creationtime', 92 | 'commenttime', 'updatetime', 'estimateddeliverymintime', 93 | 'estimateddeliverymaxtime', 'creationtime', 'estimateddeliverymintime', 94 | 'estimateddeliverymaxtime', 'endtime', 'starttime'] 95 | 96 | self.base_list_nodes = [ 97 | 'findhalfproductsresponse.halfcatalogproduct.productid', 98 | 'findhalfproductsresponse.halfproducts.product', 99 | 'getshippingcostsresponse.internationalshippingserviceoption.shipsto', 100 | 'getsingleitemresponse.itemcompatibility.compatibility', 101 | 'getsingleitemresponse.itemcompatibility.namevaluelist', 102 | 'getsingleitemresponse.variationspecifics.namevaluelist', 103 | 'getsingleitemresponse.namevaluelist.value', 104 | 'getsingleitemresponse.pictures.variationspecificpictureset', 105 | 'getmultipleitemsresponse.pictures.variationspecificpictureset', 106 | 'findreviewsandguidesresponse.reviewdetails.review', 107 | 'getshippingcostsresponse.shippingdetails.internationalshippingserviceoption', 108 | 'getshippingcostsresponse.shippingdetails.shippingserviceoption', 109 | 'getshippingcostsresponse.shippingdetails.excludeshiptolocation', 110 | 'getshippingcostsresponse.shippingserviceoption.shipsto', 111 | 'findpopularitemsresponse.itemarray.item', 112 | 'findproductsresponse.itemarray.item', 113 | 'getsingleitemresponse.item.paymentmethods', 114 | 'getmultipleitemsresponse.item.pictureurl', 115 | 'getsingleitemresponse.item.pictureurl', 116 | 'findproductsresponse.item.shiptolocations', 117 | 'getmultipleitemsresponse.item.shiptolocations', 118 | 'getsingleitemresponse.item.shiptolocations', 119 | 'getmultipleitemsresponse.item.paymentallowedsite', 120 | 'getsingleitemresponse.item.paymentallowedsite', 121 | 'getsingleitemresponse.item.excludeshiptolocation', 122 | 'getshippingcostsresponse.taxtable.taxjurisdiction', 123 | 'getsingleitemresponse.variationspecificpictureset.pictureurl', 124 | 'getmultipleitemsresponse.variationspecificpictureset.pictureurl', 125 | 'getsingleitemresponse.variations.variation', 126 | 'getmultipleitemsresponse.variations.variation', 127 | 'getsingleitemresponse.variations.pictures', 128 | 'getmultipleitemsresponse.variations.pictures', 129 | ] 130 | 131 | def build_request_headers(self, verb): 132 | headers = { 133 | "X-EBAY-API-VERSION": self.config.get('version', ''), 134 | "X-EBAY-API-APP-ID": self.config.get('appid', ''), 135 | "X-EBAY-API-SITE-ID": self.config.get('siteid', ''), 136 | "X-EBAY-API-CALL-NAME": verb, 137 | "X-EBAY-API-REQUEST-ENCODING": "XML", 138 | "Content-Type": "text/xml" 139 | } 140 | 141 | if self.config.get('trackingid'): 142 | headers.update({ 143 | "X-EBAY-API-TRACKING-ID": self.config.get('trackingid') 144 | }) 145 | 146 | if self.config.get('trackingpartnercode'): 147 | headers.update({ 148 | "X-EBAY-API-TRACKING-PARTNER-CODE": self.config.get('trackingpartnercode') 149 | }) 150 | 151 | return headers 152 | 153 | def build_request_data(self, verb, data, verb_attrs): 154 | 155 | xml = "" 156 | xml += "<" + verb + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">" 157 | xml += dict2xml(data, self.escape_xml) 158 | xml += "" 159 | 160 | return xml 161 | 162 | def warnings(self): 163 | warning_string = "" 164 | 165 | if len(self._resp_body_warnings) > 0: 166 | warning_string = "%s: %s" \ 167 | % (self.verb, ", ".join(self._resp_body_warnings)) 168 | 169 | return warning_string 170 | 171 | def _get_resp_body_errors(self): 172 | """Parses the response content to pull errors. 173 | 174 | Child classes should override this method based on what the errors in the 175 | XML response body look like. They can choose to look at the 'ack', 176 | 'Errors', 'errorMessage' or whatever other fields the service returns. 177 | the implementation below is the original code that was part of error() 178 | """ 179 | 180 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 181 | return self._resp_body_errors 182 | 183 | errors = [] 184 | warnings = [] 185 | resp_codes = [] 186 | 187 | if self.verb is None: 188 | return errors 189 | 190 | dom = self.response.dom() 191 | if dom is None: 192 | return errors 193 | 194 | for e in dom.findall('Errors'): 195 | eSeverity = None 196 | eClass = None 197 | eShortMsg = None 198 | eLongMsg = None 199 | eCode = None 200 | 201 | try: 202 | eSeverity = e.findall('SeverityCode')[0].text 203 | except IndexError: 204 | pass 205 | 206 | try: 207 | eClass = e.findall('ErrorClassification')[0].text 208 | except IndexError: 209 | pass 210 | 211 | try: 212 | eCode = e.findall('ErrorCode')[0].text 213 | except IndexError: 214 | pass 215 | 216 | try: 217 | eShortMsg = e.findall('ShortMessage')[0].text 218 | except IndexError: 219 | pass 220 | 221 | try: 222 | eLongMsg = e.findall('LongMessage')[0].text 223 | except IndexError: 224 | pass 225 | 226 | try: 227 | eCode = float(e.findall('ErrorCode')[0].text) 228 | if eCode.is_integer(): 229 | eCode = int(eCode) 230 | 231 | if eCode not in resp_codes: 232 | resp_codes.append(eCode) 233 | except IndexError: 234 | pass 235 | 236 | msg = "Class: %s, Severity: %s, Code: %s, %s%s" \ 237 | % (eClass, eSeverity, eCode, eShortMsg, eLongMsg) 238 | 239 | if eSeverity == 'Warning': 240 | warnings.append(msg) 241 | else: 242 | errors.append(msg) 243 | 244 | self._resp_body_warnings = warnings 245 | self._resp_body_errors = errors 246 | self._resp_codes = resp_codes 247 | 248 | if self.config.get('warnings') and len(warnings) > 0: 249 | log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) 250 | 251 | if self.response.reply.Ack == 'Failure': 252 | if self.config.get('errors'): 253 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 254 | return errors 255 | 256 | return [] 257 | -------------------------------------------------------------------------------- /ebaysdk/shopping/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/shopping/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/soa/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | from ebaysdk import log 10 | from ebaysdk.connection import BaseConnection 11 | from ebaysdk.config import Config 12 | from ebaysdk.utils import getNodeText, dict2xml 13 | 14 | 15 | class Connection(BaseConnection): 16 | """Connection class for a base SOA service""" 17 | 18 | def __init__(self, app_config=None, site_id='EBAY-US', debug=False, **kwargs): 19 | """SOA Connection class constructor""" 20 | 21 | super(Connection, self).__init__(method='POST', debug=debug, **kwargs) 22 | 23 | self.config = Config(domain=kwargs.get('domain', ''), 24 | connection_kwargs=kwargs, 25 | config_file=kwargs.get('config_file', 'ebay.yaml')) 26 | 27 | self.config.set('https', False) 28 | self.config.set('site_id', site_id) 29 | self.config.set('content_type', 'text/xml;charset=UTF-8') 30 | self.config.set('request_encoding', 'XML') 31 | self.config.set('response_encoding', 'XML') 32 | self.config.set('message_protocol', 'SOAP12') 33 | # http://www.ebay.com/marketplace/fundraising/v1/services', 34 | self.config.set('soap_env_str', '') 35 | 36 | ph = None 37 | pp = 80 38 | if app_config: 39 | self.load_from_app_config(app_config) 40 | ph = self.config.get('proxy_host', ph) 41 | pp = self.config.get('proxy_port', pp) 42 | 43 | # override this method, to provide setup through a config object, which 44 | # should provide a get() method for extracting constants we care about 45 | # this method should then set the .api_config[] dict (e.g. the comment 46 | # below) 47 | def load_from_app_config(self, app_config): 48 | # self.api_config['domain'] = app_config.get('API_SERVICE_DOMAIN') 49 | # self.api_config['uri'] = app_config.get('API_SERVICE_URI') 50 | pass 51 | 52 | # Note: this method will always return at least an empty object_dict! 53 | # It used to return None in some cases. If you get an empty dict, 54 | # you can use the .error() method to look for the cause. 55 | def response_dict(self): 56 | return self.response.dict() 57 | 58 | def build_request_headers(self, verb): 59 | return { 60 | 'Content-Type': self.config.get('content_type'), 61 | 'X-EBAY-SOA-SERVICE-NAME': self.config.get('service'), 62 | 'X-EBAY-SOA-OPERATION-NAME': verb, 63 | 'X-EBAY-SOA-GLOBAL-ID': self.config.get('site_id'), 64 | 'X-EBAY-SOA-REQUEST-DATA-FORMAT': self.config.get('request_encoding'), 65 | 'X-EBAY-SOA-RESPONSE-DATA-FORMAT': self.config.get('response_encoding'), 66 | 'X-EBAY-SOA-MESSAGE-PROTOCOL': self.config.get('message_protocol'), 67 | } 68 | 69 | def build_request_data(self, verb, data, verb_attrs): 70 | xml = '' 71 | xml += ' 0: 109 | warning_string = "%s: %s" \ 110 | % (self.verb, ", ".join(self._resp_body_warnings)) 111 | 112 | return warning_string 113 | 114 | def _get_resp_body_errors(self): 115 | """Parses the response content to pull errors. 116 | 117 | Child classes should override this method based on what the errors in the 118 | XML response body look like. They can choose to look at the 'ack', 119 | 'Errors', 'errorMessage' or whatever other fields the service returns. 120 | the implementation below is the original code that was part of error() 121 | 122 | 5014CoreRuntimeErrorSystem 123 | """ 124 | 125 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 126 | return self._resp_body_errors 127 | 128 | errors = [] 129 | warnings = [] 130 | resp_codes = [] 131 | 132 | if self.verb is None: 133 | return errors 134 | 135 | dom = self.response.dom() 136 | if dom is None: 137 | return errors 138 | 139 | for e in dom.findall('error'): 140 | 141 | eSeverity = None 142 | eDomain = None 143 | eMsg = None 144 | eId = None 145 | 146 | try: 147 | eSeverity = e.findall('severity')[0].text 148 | except IndexError: 149 | pass 150 | 151 | try: 152 | eDomain = e.findall('domain')[0].text 153 | except IndexError: 154 | pass 155 | 156 | try: 157 | eId = e.findall('errorId')[0].text 158 | if int(eId) not in resp_codes: 159 | resp_codes.append(int(eId)) 160 | except IndexError: 161 | pass 162 | 163 | try: 164 | eMsg = e.findall('message')[0].text 165 | except IndexError: 166 | pass 167 | 168 | msg = "Domain: %s, Severity: %s, errorId: %s, %s" \ 169 | % (eDomain, eSeverity, eId, eMsg) 170 | 171 | if eSeverity == 'Warning': 172 | warnings.append(msg) 173 | else: 174 | errors.append(msg) 175 | 176 | self._resp_body_warnings = warnings 177 | self._resp_body_errors = errors 178 | self._resp_codes = resp_codes 179 | 180 | if self.config.get('warnings') and len(warnings) > 0: 181 | log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) 182 | 183 | try: 184 | if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'): 185 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 186 | elif len(errors) > 0: 187 | if self.config.get('errors'): 188 | log.error("%s: %s\n\n" % (self.verb, "\n".join(errors))) 189 | return errors 190 | except AttributeError: 191 | pass 192 | 193 | return [] 194 | -------------------------------------------------------------------------------- /ebaysdk/soa/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/soa/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/soa/__pycache__/finditem.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/soa/__pycache__/finditem.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/soa/finditem.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import os 10 | 11 | from ebaysdk.soa import Connection as BaseConnection 12 | from ebaysdk.utils import dict2xml, getNodeText 13 | 14 | 15 | class Connection(BaseConnection): 16 | """ 17 | Not to be confused with Finding service 18 | 19 | Implements FindItemServiceNextGen 20 | 21 | https://wiki.vip.corp.ebay.com/display/apdoc/FindItemServiceNextGen 22 | 23 | This class is a bit hackish, it subclasses SOAService, but removes 24 | SOAP support. FindItemServiceNextGen works fine with standard XML 25 | and lets avoid all of the ugliness associated with SOAP. 26 | 27 | >>> from ebaysdk.shopping import Connection as Shopping 28 | >>> s = Shopping(config_file=os.environ.get('EBAY_YAML')) 29 | >>> retval = s.execute('FindPopularItems', {'QueryKeywords': 'Python'}) 30 | >>> nodes = s.response_dom().getElementsByTagName('ItemID') 31 | >>> itemIds = [getNodeText(n) for n in nodes] 32 | >>> len(itemIds) > 0 33 | True 34 | >>> f = Connection(debug=False, config_file=os.environ.get('EBAY_YAML')) 35 | >>> records = f.find_items_by_ids(itemIds) 36 | >>> len(records) > 0 37 | True 38 | """ 39 | 40 | def __init__(self, site_id='EBAY-US', debug=False, consumer_id=None, 41 | domain='apifindingcore.vip.ebay.com', **kwargs): 42 | 43 | super(Connection, self).__init__(consumer_id=consumer_id, 44 | domain=domain, 45 | app_config=None, 46 | site_id=site_id, 47 | debug=debug, **kwargs) 48 | 49 | self.config.set('domain', 'apifindingcore.vip.ebay.com') 50 | self.config.set('service', 'FindItemServiceNextGen', force=True) 51 | self.config.set('https', False) 52 | self.config.set( 53 | 'uri', "/services/search/FindItemServiceNextGen/v1", force=True) 54 | self.config.set('consumer_id', consumer_id) 55 | 56 | self.read_set = None 57 | 58 | self.datetime_nodes += ['lastupdatetime', 'timestamp'] 59 | self.base_list_nodes += ['finditemsbyidsresponse.record'] 60 | 61 | def build_request_headers(self, verb): 62 | return { 63 | "X-EBAY-SOA-SERVICE-NAME": self.config.get('service', ''), 64 | "X-EBAY-SOA-SERVICE-VERSION": self.config.get('version', ''), 65 | "X-EBAY-SOA-GLOBAL-ID": self.config.get('siteid', ''), 66 | "X-EBAY-SOA-OPERATION-NAME": verb, 67 | "X-EBAY-SOA-CONSUMER-ID": self.config.get('consumer_id', ''), 68 | "Content-Type": "text/xml" 69 | } 70 | 71 | def findItemsByIds(self, ebay_item_ids, 72 | read_set=['ITEM_ID', 'TITLE', 'SELLER_NAME', 'ALL_CATS', 'ITEM_CONDITION_NEW']): 73 | 74 | self.read_set = read_set 75 | read_set_node = [] 76 | 77 | for rtype in self.read_set: 78 | read_set_node.append({ 79 | 'member': { 80 | 'namespace': 'ItemDictionary', 81 | 'name': rtype 82 | } 83 | }) 84 | 85 | args = {'id': ebay_item_ids, 'readSet': read_set_node} 86 | self.execute('findItemsByIds', args) 87 | return self.mappedResponse() 88 | 89 | def mappedResponse(self): 90 | records = [] 91 | 92 | for r in self.response.dict().get('record', []): 93 | mydict = dict() 94 | i = 0 95 | 96 | for values_dict in r.get('value', {}): 97 | 98 | if values_dict is None: 99 | continue 100 | 101 | for key, value in values_dict.items(): 102 | value_data = None 103 | if type(value) == list: 104 | value_data = [x for x in value] 105 | else: 106 | value_data = value 107 | 108 | mydict.update({self.read_set[i]: value_data}) 109 | 110 | i = i + 1 111 | 112 | records.append(mydict) 113 | 114 | return records 115 | 116 | def find_items_by_ids(self, *args, **kwargs): 117 | return self.findItemsByIds(*args, **kwargs) 118 | 119 | def build_request_data(self, verb, data, verb_attrs): 120 | xml = "" 121 | xml += "<" + verb + "Request" 122 | xml += ' xmlns="http://www.ebay.com/marketplace/search/v1/services"' 123 | xml += '>' 124 | xml += dict2xml(data, self.escape_xml) 125 | xml += "" 126 | 127 | return xml 128 | -------------------------------------------------------------------------------- /ebaysdk/trading/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | © 2012-2013 eBay Software Foundation 5 | Authored by: Tim Keefer 6 | Licensed under CDDL 1.0 7 | ''' 8 | 9 | import os 10 | 11 | from ebaysdk import log 12 | from ebaysdk.connection import BaseConnection 13 | from ebaysdk.config import Config 14 | from ebaysdk.utils import getNodeText, dict2xml, smart_encode 15 | from ebaysdk.exception import RequestPaginationError, PaginationLimit 16 | 17 | 18 | class Connection(BaseConnection): 19 | """Trading API class 20 | 21 | API documentation: 22 | https://www.x.com/developers/ebay/products/trading-api 23 | 24 | Supported calls: 25 | AddItem 26 | ReviseItem 27 | GetUser 28 | (all others, see API docs) 29 | 30 | Doctests: 31 | >>> import datetime 32 | >>> t = Connection(config_file=os.environ.get('EBAY_YAML')) 33 | >>> response = t.execute(u'GetCharities', {'CharityID': 3897}) 34 | >>> charity_name = '' 35 | >>> if len( t.response.dom().xpath('//Name') ) > 0: 36 | ... charity_name = t.response.dom().xpath('//Name')[0].text 37 | >>> print(charity_name) 38 | Sunshine Kids Foundation 39 | >>> isinstance(response.reply.Timestamp, datetime.datetime) 40 | True 41 | >>> print(t.error()) 42 | None 43 | >>> t2 = Connection(errors=False, debug=False, config_file=os.environ.get('EBAY_YAML')) 44 | >>> response = t2.execute(u'VerifyAddItem', {}) 45 | >>> print(t2.response_codes()) 46 | [10009] 47 | 48 | Proof that utf8 errors work (calling .error() was triggering UTF8 Errors 49 | >>> t3 = Connection(token="WRONG_TOKEN", errors=False, debug=False, config_file=os.environ.get('EBAY_YAML')) 50 | >>> response = t3.execute(u'VerifyAddItem', {u'ErrorLanguage': u'de_DE'}) 51 | >>> error = t3.error() 52 | >>> error.startswith('VerifyAddItem: Class: RequestError, Severity:') 53 | True 54 | """ 55 | 56 | def __init__(self, **kwargs): 57 | """Trading class constructor. 58 | 59 | Keyword arguments: 60 | domain -- API endpoint (default: api.ebay.com) 61 | config_file -- YAML defaults (default: ebay.yaml) 62 | debug -- debugging enabled (default: False) 63 | warnings -- warnings enabled (default: False) 64 | uri -- API endpoint uri (default: /ws/api.dll) 65 | appid -- eBay application id 66 | devid -- eBay developer id 67 | certid -- eBay cert id 68 | token -- eBay application/user token 69 | siteid -- eBay country site id (default: 0 (US)) 70 | compatibility -- version number (default: 648) 71 | https -- execute of https (default: True) 72 | proxy_host -- proxy hostname 73 | proxy_port -- proxy port number 74 | timeout -- HTTP request timeout (default: 20) 75 | parallel -- ebaysdk parallel object 76 | response_encoding -- API encoding (default: XML) 77 | request_encoding -- API encoding (default: XML) 78 | """ 79 | super(Connection, self).__init__(method='POST', **kwargs) 80 | 81 | self.config = Config(domain=kwargs.get('domain', 'api.ebay.com'), 82 | connection_kwargs=kwargs, 83 | config_file=kwargs.get('config_file', 'ebay.yaml')) 84 | 85 | # override yaml defaults with args sent to the constructor 86 | self.config.set('domain', kwargs.get('domain', 'api.ebay.com')) 87 | self.config.set('uri', '/ws/api.dll') 88 | self.config.set('warnings', True) 89 | self.config.set('errors', True) 90 | self.config.set('https', True) 91 | self.config.set('siteid', '0') 92 | self.config.set('response_encoding', 'XML') 93 | self.config.set('request_encoding', 'XML') 94 | self.config.set('proxy_host', None) 95 | self.config.set('proxy_port', None) 96 | self.config.set('token', None) 97 | self.config.set('iaf_token', None) 98 | self.config.set('appid', None) 99 | self.config.set('devid', None) 100 | self.config.set('certid', None) 101 | self.config.set('compatibility', '837') 102 | self.config.set( 103 | 'doc_url', 'http://developer.ebay.com/devzone/xml/docs/reference/ebay/index.html') 104 | 105 | self.datetime_nodes = [ 106 | 'shippingtime', 107 | 'starttime', 108 | 'endtime', 109 | 'scheduletime', 110 | 'createdtime', 111 | 'hardexpirationtime', 112 | 'invoicedate', 113 | 'begindate', 114 | 'enddate', 115 | 'startcreationtime', 116 | 'endcreationtime', 117 | 'endtimefrom', 118 | 'endtimeto', 119 | 'updatetime', 120 | 'lastupdatetime', 121 | 'lastmodifiedtime', 122 | 'modtimefrom', 123 | 'modtimeto', 124 | 'createtimefrom', 125 | 'createtimeto', 126 | 'starttimefrom', 127 | 'starttimeto', 128 | 'timeto', 129 | 'paymenttimefrom', 130 | 'paymenttimeto', 131 | 'inventorycountlastcalculateddate', 132 | 'registrationdate', 133 | 'timefrom', 134 | 'timestamp', 135 | 'messagecreationtime', 136 | 'resolutiontime', 137 | 'date', 138 | 'bankmodifydate', 139 | 'creditcardexpiration', 140 | 'creditcardmodifydate', 141 | 'lastpaymentdate', 142 | 'submittedtime', 143 | 'announcementstarttime', 144 | 'eventtime', 145 | 'periodicstartdate', 146 | 'modtime', 147 | 'expirationtime', 148 | 'creationtime', 149 | 'lastusedtime', 150 | 'disputecreatedtime', 151 | 'disputemodifiedtime', 152 | 'externaltransactiontime', 153 | 'commenttime', 154 | 'lastbidtime', 155 | 'time', 156 | 'creationdate', 157 | 'lastmodifieddate', 158 | 'receivedate', 159 | 'expirationdate', 160 | 'resolutiondate', 161 | 'lastreaddate', 162 | 'userforwarddate', 163 | 'itemendtime', 164 | 'userresponsedate', 165 | 'nextretrytime', 166 | 'deliverytime', 167 | 'timebid', 168 | 'paidtime', 169 | 'shippedtime', 170 | 'expectedreleasedate', 171 | 'paymenttime', 172 | 'promotionalsalestarttime', 173 | 'promotionalsaleendtime', 174 | 'refundtime', 175 | 'refundrequestedtime', 176 | 'refundcompletiontime', 177 | 'estimatedrefundcompletiontime', 178 | 'lastemailsenttime', 179 | 'sellerinvoicetime', 180 | 'estimateddeliverydate', 181 | 'printedtime', 182 | 'deliverydate', 183 | 'refundgrantedtime', 184 | 'scheduleddeliverytimemin', 185 | 'scheduleddeliverytimemax', 186 | 'actualdeliverytime', 187 | 'usebydate', 188 | 'lastopenedtime', 189 | 'returndate', 190 | 'revocationtime', 191 | 'lasttimemodified', 192 | 'createddate', 193 | 'invoicesenttime', 194 | 'acceptedtime', 195 | 'sellerebaypaymentprocessenabletime', 196 | 'useridlastchanged', 197 | 'actionrequiredby', 198 | ] 199 | 200 | self.base_list_nodes = [ 201 | 'getmymessagesresponse.abstractrequest.detaillevel', 202 | 'getaccountresponse.abstractrequest.outputselector', 203 | 'getadformatleadsresponse.abstractrequest.outputselector', 204 | 'getallbiddersresponse.abstractrequest.outputselector', 205 | 'getbestoffersresponse.abstractrequest.outputselector', 206 | 'getbidderlistresponse.abstractrequest.outputselector', 207 | 'getcategoriesresponse.abstractrequest.outputselector', 208 | 'getcategoryfeaturesresponse.abstractrequest.outputselector', 209 | 'getcategorylistingsresponse.abstractrequest.outputselector', 210 | 'getcrosspromotionsresponse.abstractrequest.outputselector', 211 | 'getfeedbackresponse.abstractrequest.outputselector', 212 | 'gethighbiddersresponse.abstractrequest.outputselector', 213 | 'getitemresponse.abstractrequest.outputselector', 214 | 'getitemsawaitingfeedbackresponse.abstractrequest.outputselector', 215 | 'getitemshippingresponse.abstractrequest.outputselector', 216 | 'getitemtransactionsresponse.abstractrequest.outputselector', 217 | 'getmembermessagesresponse.abstractrequest.outputselector', 218 | 'getmyebaybuyingresponse.abstractrequest.outputselector', 219 | 'getmyebaysellingresponse.abstractrequest.outputselector', 220 | 'getmyebaysellingresponse.activelist.itemarray.item', 221 | 'getmymessagesresponse.abstractrequest.outputselector', 222 | 'getnotificationpreferencesresponse.abstractrequest.outputselector', 223 | 'getordersresponse.abstractrequest.outputselector', 224 | 'getordertransactionsresponse.abstractrequest.outputselector', 225 | 'getproductsresponse.abstractrequest.outputselector', 226 | 'getsearchresultsresponse.abstractrequest.outputselector', 227 | 'getsellereventsresponse.abstractrequest.outputselector', 228 | 'getsellerlistresponse.abstractrequest.outputselector', 229 | 'getsellerpaymentsresponse.abstractrequest.outputselector', 230 | 'getsellertransactionsresponse.abstractrequest.outputselector', 231 | 'getmessagepreferencesresponse.asqpreferences.subject', 232 | 'getaccountresponse.accountentries.accountentry', 233 | 'getaccountresponse.accountsummary.additionalaccount', 234 | 'additemresponse.additemresponsecontainer.discountreason', 235 | 'additemsresponse.additemresponsecontainer.discountreason', 236 | 'setnotificationpreferencesresponse.applicationdeliverypreferences.deliveryurldetails', 237 | 'additemresponse.attributearray.attribute', 238 | 'additemsresponse.attributearray.attribute', 239 | 'verifyadditemresponse.attributearray.attribute', 240 | 'additemresponse.attribute.value', 241 | 'additemsresponse.attribute.value', 242 | 'addsellingmanagertemplateresponse.attribute.value', 243 | 'addliveauctionitemresponse.attribute.value', 244 | 'getitemrecommendationsresponse.attribute.value', 245 | 'verifyadditemresponse.attribute.value', 246 | 'addfixedpriceitemresponse.attribute.value', 247 | 'relistfixedpriceitemresponse.attribute.value', 248 | 'revisefixedpriceitemresponse.attribute.value', 249 | 'getfeedbackresponse.averageratingdetailarray.averageratingdetails', 250 | 'getfeedbackresponse.averageratingsummary.averageratingdetails', 251 | 'respondtobestofferresponse.bestofferarray.bestoffer', 252 | 'getliveauctionbiddersresponse.bidderdetailarray.bidderdetail', 253 | 'getallbiddersresponse.biddingsummary.itembiddetails', 254 | 'getsellerdashboardresponse.buyersatisfactiondashboard.alert', 255 | 'getshippingdiscountprofilesresponse.calculatedshippingdiscount.discountprofile', 256 | 'getcategoriesresponse.categoryarray.category', 257 | 'getcategoryfeaturesresponse.categoryfeature.listingduration', 258 | 'getcategoryfeaturesresponse.categoryfeature.paymentmethod', 259 | 'getcategoriesresponse.category.categoryparentid', 260 | 'getsuggestedcategoriesresponse.category.categoryparentname', 261 | 'getcategory2csresponse.category.productfinderids', 262 | 'getcategory2csresponse.category.characteristicssets', 263 | 'getproductfamilymembersresponse.characteristicsset.characteristics', 264 | 'getproductsearchpageresponse.characteristicsset.characteristics', 265 | 'getproductsearchresultsresponse.characteristicsset.characteristics', 266 | 'getuserresponse.charityaffiliationdetails.charityaffiliationdetail', 267 | 'getbidderlistresponse.charityaffiliations.charityid', 268 | 'setcharitiesresponse.charityinfo.nonprofitaddress', 269 | 'setcharitiesresponse.charityinfo.nonprofitsocialaddress', 270 | 'getcategoryfeaturesresponse.conditionvalues.condition', 271 | 'getbidderlistresponse.crosspromotions.promoteditem', 272 | 'getuserdisputesresponse.disputearray.dispute', 273 | 'getuserdisputesresponse.dispute.disputeresolution', 274 | 'getdisputeresponse.dispute.disputemessage', 275 | 'setsellingmanagerfeedbackoptionsresponse.feedbackcommentarray.storedcommenttext', 276 | 'getfeedbackresponse.feedbackdetailarray.feedbackdetail', 277 | 'getfeedbackresponse.feedbackperiodarray.feedbackperiod', 278 | 'addfixedpriceitemresponse.fees.fee', 279 | 'additemresponse.fees.fee', 280 | 'additemsresponse.fees.fee', 281 | 'addliveauctionitemresponse.fees.fee', 282 | 'relistfixedpriceitemresponse.fees.fee', 283 | 'relistitemresponse.fees.fee', 284 | 'revisefixedpriceitemresponse.fees.fee', 285 | 'reviseitemresponse.fees.fee', 286 | 'reviseliveauctionitemresponse.fees.fee', 287 | 'verifyaddfixedpriceitemresponse.fees.fee', 288 | 'verifyadditemresponse.fees.fee', 289 | 'reviseinventorystatusresponse.fees.fee', 290 | 'verifyrelistitemresponse.fees.fee', 291 | 'getshippingdiscountprofilesresponse.flatshippingdiscount.discountprofile', 292 | 'getitemrecommendationsresponse.getrecommendationsrequestcontainer.recommendationengine', 293 | 'getitemrecommendationsresponse.getrecommendationsrequestcontainer.deletedfield', 294 | 'getuserresponse.integratedmerchantcreditcardinfo.supportedsite', 295 | 'sendinvoiceresponse.internationalshippingserviceoptions.shiptolocation', 296 | 'reviseinventorystatusresponse.inventoryfees.fee', 297 | 'getbidderlistresponse.itemarray.item', 298 | 'getbestoffersresponse.itembestoffersarray.itembestoffers', 299 | 'addfixedpriceitemresponse.itemcompatibilitylist.compatibility', 300 | 'additemresponse.itemcompatibilitylist.compatibility', 301 | 'additemfromsellingmanagertemplateresponse.itemcompatibilitylist.compatibility', 302 | 'additemsresponse.itemcompatibilitylist.compatibility', 303 | 'addsellingmanagertemplateresponse.itemcompatibilitylist.compatibility', 304 | 'relistfixedpriceitemresponse.itemcompatibilitylist.compatibility', 305 | 'relistitemresponse.itemcompatibilitylist.compatibility', 306 | 'revisefixedpriceitemresponse.itemcompatibilitylist.compatibility', 307 | 'reviseitemresponse.itemcompatibilitylist.compatibility', 308 | 'revisesellingmanagertemplateresponse.itemcompatibilitylist.compatibility', 309 | 'verifyaddfixedpriceitemresponse.itemcompatibilitylist.compatibility', 310 | 'verifyadditemresponse.itemcompatibilitylist.compatibility', 311 | 'verifyrelistitemresponse.itemcompatibilitylist.compatibility', 312 | 'addfixedpriceitemresponse.itemcompatibility.namevaluelist', 313 | 'additemresponse.itemcompatibility.namevaluelist', 314 | 'additemfromsellingmanagertemplateresponse.itemcompatibility.namevaluelist', 315 | 'additemsresponse.itemcompatibility.namevaluelist', 316 | 'addsellingmanagertemplateresponse.itemcompatibility.namevaluelist', 317 | 'relistfixedpriceitemresponse.itemcompatibility.namevaluelist', 318 | 'relistitemresponse.itemcompatibility.namevaluelist', 319 | 'revisefixedpriceitemresponse.itemcompatibility.namevaluelist', 320 | 'reviseitemresponse.itemcompatibility.namevaluelist', 321 | 'revisesellingmanagertemplateresponse.itemcompatibility.namevaluelist', 322 | 'verifyadditemresponse.itemcompatibility.namevaluelist', 323 | 'verifyrelistitemresponse.itemcompatibility.namevaluelist', 324 | 'getpromotionalsaledetailsresponse.itemidarray.itemid', 325 | 'leavefeedbackresponse.itemratingdetailarray.itemratingdetails', 326 | 'getordertransactionsresponse.itemtransactionidarray.itemtransactionid', 327 | 'addfixedpriceitemresponse.item.giftservices', 328 | 'additemresponse.item.giftservices', 329 | 'additemsresponse.item.giftservices', 330 | 'addsellingmanagertemplateresponse.item.giftservices', 331 | 'getitemrecommendationsresponse.item.giftservices', 332 | 'relistfixedpriceitemresponse.item.giftservices', 333 | 'relistitemresponse.item.giftservices', 334 | 'revisefixedpriceitemresponse.item.giftservices', 335 | 'reviseitemresponse.item.giftservices', 336 | 'revisesellingmanagertemplateresponse.item.giftservices', 337 | 'verifyadditemresponse.item.giftservices', 338 | 'verifyrelistitemresponse.item.giftservices', 339 | 'addfixedpriceitemresponse.item.listingenhancement', 340 | 'additemresponse.item.listingenhancement', 341 | 'additemsresponse.item.listingenhancement', 342 | 'addsellingmanagertemplateresponse.item.listingenhancement', 343 | 'getitemrecommendationsresponse.item.listingenhancement', 344 | 'relistfixedpriceitemresponse.item.listingenhancement', 345 | 'relistitemresponse.item.listingenhancement', 346 | 'revisefixedpriceitemresponse.item.listingenhancement', 347 | 'reviseitemresponse.item.listingenhancement', 348 | 'revisesellingmanagertemplateresponse.item.listingenhancement', 349 | 'verifyadditemresponse.item.listingenhancement', 350 | 'verifyrelistitemresponse.item.listingenhancement', 351 | 'addfixedpriceitemresponse.item.paymentmethods', 352 | 'additemresponse.item.paymentmethods', 353 | 'additemfromsellingmanagertemplateresponse.item.paymentmethods', 354 | 'additemsresponse.item.paymentmethods', 355 | 'addsellingmanagertemplateresponse.item.paymentmethods', 356 | 'relistfixedpriceitemresponse.item.paymentmethods', 357 | 'relistitemresponse.item.paymentmethods', 358 | 'revisefixedpriceitemresponse.item.paymentmethods', 359 | 'reviseitemresponse.item.paymentmethods', 360 | 'verifyadditemresponse.item.paymentmethods', 361 | 'verifyrelistitemresponse.item.paymentmethods', 362 | 'addfixedpriceitemresponse.item.shiptolocations', 363 | 'additemresponse.item.shiptolocations', 364 | 'additemsresponse.item.shiptolocations', 365 | 'addsellingmanagertemplateresponse.item.shiptolocations', 366 | 'getitemrecommendationsresponse.item.shiptolocations', 367 | 'relistfixedpriceitemresponse.item.shiptolocations', 368 | 'relistitemresponse.item.shiptolocations', 369 | 'revisefixedpriceitemresponse.item.shiptolocations', 370 | 'reviseitemresponse.item.shiptolocations', 371 | 'revisesellingmanagertemplateresponse.item.shiptolocations', 372 | 'verifyadditemresponse.item.shiptolocations', 373 | 'verifyrelistitemresponse.item.shiptolocations', 374 | 'addfixedpriceitemresponse.item.skypecontactoption', 375 | 'additemresponse.item.skypecontactoption', 376 | 'additemsresponse.item.skypecontactoption', 377 | 'addsellingmanagertemplateresponse.item.skypecontactoption', 378 | 'relistfixedpriceitemresponse.item.skypecontactoption', 379 | 'relistitemresponse.item.skypecontactoption', 380 | 'revisefixedpriceitemresponse.item.skypecontactoption', 381 | 'reviseitemresponse.item.skypecontactoption', 382 | 'revisesellingmanagertemplateresponse.item.skypecontactoption', 383 | 'verifyadditemresponse.item.skypecontactoption', 384 | 'verifyrelistitemresponse.item.skypecontactoption', 385 | 'addfixedpriceitemresponse.item.crossbordertrade', 386 | 'additemresponse.item.crossbordertrade', 387 | 'additemsresponse.item.crossbordertrade', 388 | 'addsellingmanagertemplateresponse.item.crossbordertrade', 389 | 'relistfixedpriceitemresponse.item.crossbordertrade', 390 | 'relistitemresponse.item.crossbordertrade', 391 | 'revisefixedpriceitemresponse.item.crossbordertrade', 392 | 'reviseitemresponse.item.crossbordertrade', 393 | 'revisesellingmanagertemplateresponse.item.crossbordertrade', 394 | 'verifyadditemresponse.item.crossbordertrade', 395 | 'verifyrelistitemresponse.item.crossbordertrade', 396 | 'getitemresponse.item.paymentallowedsite', 397 | 'getsellingmanagertemplatesresponse.item.paymentallowedsite', 398 | 'getcategoryfeaturesresponse.listingdurationdefinition.duration', 399 | 'getcategoryfeaturesresponse.listingdurationdefinitions.listingduration', 400 | 'getcategoryfeaturesresponse.listingenhancementdurationreference.duration', 401 | 'addfixedpriceitemresponse.listingrecommendation.value', 402 | 'additemresponse.listingrecommendation.value', 403 | 'additemsresponse.listingrecommendation.value', 404 | 'relistfixedpriceitemresponse.listingrecommendation.value', 405 | 'relistitemresponse.listingrecommendation.value', 406 | 'revisefixedpriceitemresponse.listingrecommendation.value', 407 | 'reviseitemresponse.listingrecommendation.value', 408 | 'verifyadditemresponse.listingrecommendation.value', 409 | 'verifyaddfixedpriceitemresponse.listingrecommendation.value', 410 | 'verifyrelistitemresponse.listingrecommendation.value', 411 | 'addfixedpriceitemresponse.listingrecommendations.recommendation', 412 | 'additemresponse.listingrecommendations.recommendation', 413 | 'additemsresponse.listingrecommendations.recommendation', 414 | 'relistfixedpriceitemresponse.listingrecommendations.recommendation', 415 | 'relistitemresponse.listingrecommendations.recommendation', 416 | 'revisefixedpriceitemresponse.listingrecommendations.recommendation', 417 | 'reviseitemresponse.listingrecommendations.recommendation', 418 | 'verifyadditemresponse.listingrecommendations.recommendation', 419 | 'verifyaddfixedpriceitemresponse.listingrecommendations.recommendation', 420 | 'verifyrelistitemresponse.listingrecommendations.recommendation', 421 | 'getitemrecommendationsresponse.listingtiparray.listingtip', 422 | 'getnotificationsusageresponse.markupmarkdownhistory.markupmarkdownevent', 423 | 'getebaydetailsresponse.maximumbuyerpolicyviolationsdetails.policyviolationduration', 424 | 'getebaydetailsresponse.maximumitemrequirementsdetails.maximumitemcount', 425 | 'getebaydetailsresponse.maximumitemrequirementsdetails.minimumfeedbackscore', 426 | 'getebaydetailsresponse.maximumunpaiditemstrikescountdetails.count', 427 | 'getebaydetailsresponse.maximumunpaiditemstrikesinfodetails.maximumunpaiditemstrikesduration', 428 | 'getadformatleadsresponse.membermessageexchangearray.membermessageexchange', 429 | 'getadformatleadsresponse.membermessageexchange.response', 430 | 'getmembermessagesresponse.membermessageexchange.messagemedia', 431 | 'addmembermessageaaqtopartnerresponse.membermessage.recipientid', 432 | 'addmembermessagertqresponse.membermessage.recipientid', 433 | 'addmembermessagesaaqtobidderresponse.membermessage.recipientid', 434 | 'addmembermessageaaqtopartnerresponse.membermessage.messagemedia', 435 | 'addmembermessagertqresponse.membermessage.messagemedia', 436 | 'addmembermessagecemresponse.membermessage.messagemedia', 437 | 'addmembermessageaaqtosellerresponse.membermessage.messagemedia', 438 | 'getebaydetailsresponse.minimumfeedbackscoredetails.feedbackscore', 439 | 'relistfixedpriceitemresponse.modifynamearray.modifyname', 440 | 'revisefixedpriceitemresponse.modifynamearray.modifyname', 441 | 'getmymessagesresponse.mymessagesexternalmessageidarray.externalmessageid', 442 | 'getmymessagesresponse.mymessagesmessagearray.message', 443 | 'deletemymessagesresponse.mymessagesmessageidarray.messageid', 444 | 'getmymessagesresponse.mymessagesmessage.messagemedia', 445 | 'getmymessagesresponse.mymessagessummary.foldersummary', 446 | 'getmyebaybuyingresponse.myebayfavoritesearchlist.favoritesearch', 447 | 'getmyebaybuyingresponse.myebayfavoritesearch.searchflag', 448 | 'getmyebaybuyingresponse.myebayfavoritesearch.sellerid', 449 | 'getmyebaybuyingresponse.myebayfavoritesearch.selleridexclude', 450 | 'getmyebaybuyingresponse.myebayfavoritesellerlist.favoriteseller', 451 | 'getcategoryspecificsresponse.namerecommendation.valuerecommendation', 452 | 'getitemrecommendationsresponse.namerecommendation.valuerecommendation', 453 | 'addfixedpriceitemresponse.namevaluelistarray.namevaluelist', 454 | 'additemresponse.namevaluelistarray.namevaluelist', 455 | 'additemsresponse.namevaluelistarray.namevaluelist', 456 | 'addsellingmanagertemplateresponse.namevaluelistarray.namevaluelist', 457 | 'addliveauctionitemresponse.namevaluelistarray.namevaluelist', 458 | 'relistfixedpriceitemresponse.namevaluelistarray.namevaluelist', 459 | 'relistitemresponse.namevaluelistarray.namevaluelist', 460 | 'revisefixedpriceitemresponse.namevaluelistarray.namevaluelist', 461 | 'reviseitemresponse.namevaluelistarray.namevaluelist', 462 | 'revisesellingmanagertemplateresponse.namevaluelistarray.namevaluelist', 463 | 'reviseliveauctionitemresponse.namevaluelistarray.namevaluelist', 464 | 'verifyaddfixedpriceitemresponse.namevaluelistarray.namevaluelist', 465 | 'verifyadditemresponse.namevaluelistarray.namevaluelist', 466 | 'verifyrelistitemresponse.namevaluelistarray.namevaluelist', 467 | 'additemresponse.namevaluelist.value', 468 | 'additemfromsellingmanagertemplateresponse.namevaluelist.value', 469 | 'additemsresponse.namevaluelist.value', 470 | 'addsellingmanagertemplateresponse.namevaluelist.value', 471 | 'addliveauctionitemresponse.namevaluelist.value', 472 | 'relistitemresponse.namevaluelist.value', 473 | 'reviseitemresponse.namevaluelist.value', 474 | 'revisesellingmanagertemplateresponse.namevaluelist.value', 475 | 'reviseliveauctionitemresponse.namevaluelist.value', 476 | 'verifyadditemresponse.namevaluelist.value', 477 | 'verifyrelistitemresponse.namevaluelist.value', 478 | 'getnotificationsusageresponse.notificationdetailsarray.notificationdetails', 479 | 'setnotificationpreferencesresponse.notificationenablearray.notificationenable', 480 | 'setnotificationpreferencesresponse.notificationuserdata.summaryschedule', 481 | 'getebaydetailsresponse.numberofpolicyviolationsdetails.count', 482 | 'getallbiddersresponse.offerarray.offer', 483 | 'gethighbiddersresponse.offerarray.offer', 484 | 'getordersresponse.orderarray.order', 485 | 'getordersresponse.orderarray.order.transactionarray.transaction', 486 | 'getordersresponse.orderidarray.orderid', 487 | 'getmyebaybuyingresponse.ordertransactionarray.ordertransaction', 488 | 'addorderresponse.order.paymentmethods', 489 | 'getordertransactionsresponse.order.externaltransaction', 490 | 'getordersresponse.order.externaltransaction', 491 | 'getordersresponse.paymentinformationcode.payment', 492 | 'getordersresponse.paymentinformation.payment', 493 | 'getordersresponse.paymenttransactioncode.paymentreferenceid', 494 | 'getordersresponse.paymenttransaction.paymentreferenceid', 495 | 'getsellerdashboardresponse.performancedashboard.site', 496 | 'getordersresponse.pickupdetails.pickupoptions', 497 | 'additemresponse.picturedetails.pictureurl', 498 | 'additemsresponse.picturedetails.pictureurl', 499 | 'addsellingmanagertemplateresponse.picturedetails.pictureurl', 500 | 'getitemrecommendationsresponse.picturedetails.pictureurl', 501 | 'relistitemresponse.picturedetails.pictureurl', 502 | 'reviseitemresponse.picturedetails.pictureurl', 503 | 'revisesellingmanagertemplateresponse.picturedetails.pictureurl', 504 | 'verifyadditemresponse.picturedetails.pictureurl', 505 | 'verifyrelistitemresponse.picturedetails.pictureurl', 506 | 'getitemresponse.picturedetails.externalpictureurl', 507 | 'addfixedpriceitemresponse.pictures.variationspecificpictureset', 508 | 'verifyaddfixedpriceitemresponse.pictures.variationspecificpictureset', 509 | 'relistfixedpriceitemresponse.pictures.variationspecificpictureset', 510 | 'revisefixedpriceitemresponse.pictures.variationspecificpictureset', 511 | 'getsellerdashboardresponse.powersellerdashboard.alert', 512 | 'getbidderlistresponse.productlistingdetails.copyright', 513 | 'getitemrecommendationsresponse.productrecommendations.product', 514 | 'addfixedpriceitemresponse.productsuggestions.productsuggestion', 515 | 'additemresponse.productsuggestions.productsuggestion', 516 | 'relistfixedpriceitemresponse.productsuggestions.productsuggestion', 517 | 'relistitemresponse.productsuggestions.productsuggestion', 518 | 'revisefixedpriceitemresponse.productsuggestions.productsuggestion', 519 | 'reviseitemresponse.productsuggestions.productsuggestion', 520 | 'verifyadditemresponse.productsuggestions.productsuggestion', 521 | 'verifyrelistitemresponse.productsuggestions.productsuggestion', 522 | 'getpromotionalsaledetailsresponse.promotionalsalearray.promotionalsale', 523 | 'addfixedpriceitemresponse.recommendation.recommendedvalue', 524 | 'additemresponse.recommendation.recommendedvalue', 525 | 'additemsresponse.recommendation.recommendedvalue', 526 | 'relistfixedpriceitemresponse.recommendation.recommendedvalue', 527 | 'relistitemresponse.recommendation.recommendedvalue', 528 | 'revisefixedpriceitemresponse.recommendation.recommendedvalue', 529 | 'reviseitemresponse.recommendation.recommendedvalue', 530 | 'verifyadditemresponse.recommendation.recommendedvalue', 531 | 'verifyaddfixedpriceitemresponse.recommendation.recommendedvalue', 532 | 'verifyrelistitemresponse.recommendation.recommendedvalue', 533 | 'getcategoryspecificsresponse.recommendationvalidationrules.relationship', 534 | 'getitemrecommendationsresponse.recommendationvalidationrules.relationship', 535 | 'getcategoryspecificsresponse.recommendations.namerecommendation', 536 | 'getitemrecommendationsresponse.recommendations.namerecommendation', 537 | 'getuserresponse.recoupmentpolicyconsent.site', 538 | 'getordersresponse.refundarray.refund', 539 | 'getordersresponse.refundfundingsourcearray.refundfundingsource', 540 | 'getitemtransactionsresponse.refundfundingsourcearray.refundfundingsource', 541 | 'getordertransactionsresponse.refundfundingsourcearray.refundfundingsource', 542 | 'getsellertransactionsresponse.refundfundingsourcearray.refundfundingsource', 543 | 'getordersresponse.refundinformation.refund', 544 | 'getordersresponse.refundlinearray.refundline', 545 | 'getitemtransactionsresponse.refundlinearray.refundline', 546 | 'getordertransactionsresponse.refundlinearray.refundline', 547 | 'getsellertransactionsresponse.refundlinearray.refundline', 548 | 'getordersresponse.refundtransactionarray.refundtransaction', 549 | 'getitemtransactionsresponse.refundtransactionarray.refundtransaction', 550 | 'getordertransactionsresponse.refundtransactionarray.refundtransaction', 551 | 'getsellertransactionsresponse.refundtransactionarray.refundtransaction', 552 | 'getordersresponse.requiredselleractionarray.requiredselleraction', 553 | 'getebaydetailsresponse.returnpolicydetails.refund', 554 | 'getebaydetailsresponse.returnpolicydetails.returnswithin', 555 | 'getebaydetailsresponse.returnpolicydetails.returnsaccepted', 556 | 'getebaydetailsresponse.returnpolicydetails.warrantyoffered', 557 | 'getebaydetailsresponse.returnpolicydetails.warrantytype', 558 | 'getebaydetailsresponse.returnpolicydetails.warrantyduration', 559 | 'getebaydetailsresponse.returnpolicydetails.shippingcostpaidby', 560 | 'getebaydetailsresponse.returnpolicydetails.restockingfeevalue', 561 | 'getsellertransactionsresponse.skuarray.sku', 562 | 'getsellerlistresponse.skuarray.sku', 563 | 'getsellerdashboardresponse.selleraccountdashboard.alert', 564 | 'getitemtransactionsresponse.sellerdiscounts.sellerdiscount', 565 | 'getordersresponse.sellerdiscounts.sellerdiscount', 566 | 'getordertransactionsresponse.sellerdiscounts.sellerdiscount', 567 | 'getsellertransactionsresponse.sellerdiscounts.sellerdiscount', 568 | 'getuserpreferencesresponse.sellerexcludeshiptolocationpreferences.excludeshiptolocation', 569 | 'getuserpreferencesresponse.sellerfavoriteitempreferences.favoriteitemid', 570 | 'getfeedbackresponse.sellerratingsummaryarray.averageratingsummary', 571 | 'getbidderlistresponse.sellerebaypaymentprocessconsentcode.useragreementinfo', 572 | 'getsellingmanagertemplateautomationruleresponse.sellingmanagerautolistaccordingtoschedule.dayofweek', 573 | 'getsellingmanagerinventoryfolderresponse.sellingmanagerfolderdetails.childfolder', 574 | 'revisesellingmanagerinventoryfolderresponse.sellingmanagerfolderdetails.childfolder', 575 | 'getsellingmanagersalerecordresponse.sellingmanagersoldorder.sellingmanagersoldtransaction', 576 | 'getsellingmanagersoldlistingsresponse.sellingmanagersoldorder.sellingmanagersoldtransaction', 577 | 'getsellingmanagersalerecordresponse.sellingmanagersoldorder.vatrate', 578 | 'getsellingmanagersoldlistingsresponse.sellingmanagersoldtransaction.listedon', 579 | 'getsellingmanagertemplatesresponse.sellingmanagertemplatedetailsarray.sellingmanagertemplatedetails', 580 | 'completesaleresponse.shipmentlineitem.lineitem', 581 | 'addshipmentresponse.shipmentlineitem.lineitem', 582 | 'reviseshipmentresponse.shipmentlineitem.lineitem', 583 | 'revisesellingmanagersalerecordresponse.shipmentlineitem.lineitem', 584 | 'setshipmenttrackinginforesponse.shipmentlineitem.lineitem', 585 | 'completesaleresponse.shipment.shipmenttrackingdetails', 586 | 'getitemresponse.shippingdetails.shippingserviceoptions', 587 | 'getsellingmanagertemplatesresponse.shippingdetails.shippingserviceoptions', 588 | 'addfixedpriceitemresponse.shippingdetails.internationalshippingserviceoption', 589 | 'additemresponse.shippingdetails.internationalshippingserviceoption', 590 | 'additemsresponse.shippingdetails.internationalshippingserviceoption', 591 | 'addsellingmanagertemplateresponse.shippingdetails.internationalshippingserviceoption', 592 | 'addorderresponse.shippingdetails.internationalshippingserviceoption', 593 | 'getitemrecommendationsresponse.shippingdetails.internationalshippingserviceoption', 594 | 'relistfixedpriceitemresponse.shippingdetails.internationalshippingserviceoption', 595 | 'relistitemresponse.shippingdetails.internationalshippingserviceoption', 596 | 'revisefixedpriceitemresponse.shippingdetails.internationalshippingserviceoption', 597 | 'reviseitemresponse.shippingdetails.internationalshippingserviceoption', 598 | 'revisesellingmanagertemplateresponse.shippingdetails.internationalshippingserviceoption', 599 | 'verifyadditemresponse.shippingdetails.internationalshippingserviceoption', 600 | 'verifyrelistitemresponse.shippingdetails.internationalshippingserviceoption', 601 | 'getsellerlistresponse.shippingdetails.excludeshiptolocation', 602 | 'getitemtransactionsresponse.shippingdetails.shipmenttrackingdetails', 603 | 'getsellertransactionsresponse.shippingdetails.shipmenttrackingdetails', 604 | 'getshippingdiscountprofilesresponse.shippinginsurance.flatrateinsurancerangecost', 605 | 'addfixedpriceitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 606 | 'additemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 607 | 'additemsresponse.shippingservicecostoverridelist.shippingservicecostoverride', 608 | 'verifyadditemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 609 | 'verifyaddfixedpriceitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 610 | 'verifyrelistitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 611 | 'relistfixedpriceitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 612 | 'relistitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 613 | 'revisefixedpriceitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 614 | 'reviseitemresponse.shippingservicecostoverridelist.shippingservicecostoverride', 615 | 'addsellingmanagertemplateresponse.shippingservicecostoverridelist.shippingservicecostoverride', 616 | 'revisesellingmanagertemplateresponse.shippingservicecostoverridelist.shippingservicecostoverride', 617 | 'getebaydetailsresponse.shippingservicedetails.servicetype', 618 | 'getebaydetailsresponse.shippingservicedetails.shippingpackage', 619 | 'getebaydetailsresponse.shippingservicedetails.shippingcarrier', 620 | 'getebaydetailsresponse.shippingservicedetails.deprecationdetails', 621 | 'getebaydetailsresponse.shippingservicedetails.shippingservicepackagedetails', 622 | 'getordersresponse.shippingserviceoptions.shippingpackageinfo', 623 | 'getcategoryfeaturesresponse.sitedefaults.listingduration', 624 | 'getcategoryfeaturesresponse.sitedefaults.paymentmethod', 625 | 'uploadsitehostedpicturesresponse.sitehostedpicturedetails.picturesetmember', 626 | 'getcategory2csresponse.sitewidecharacteristics.excludecategoryid', 627 | 'getstoreoptionsresponse.storecolorschemearray.colorscheme', 628 | 'getstoreresponse.storecustomcategoryarray.customcategory', 629 | 'getstoreresponse.storecustomcategory.childcategory', 630 | 'setstorecategoriesresponse.storecustomcategory.childcategory', 631 | 'setstoreresponse.storecustomlistingheader.linktoinclude', 632 | 'getstorecustompageresponse.storecustompagearray.custompage', 633 | 'getstoreoptionsresponse.storelogoarray.logo', 634 | 'getcategoryfeaturesresponse.storeownerextendedlistingdurations.duration', 635 | 'getstoreoptionsresponse.storesubscriptionarray.subscription', 636 | 'getstoreoptionsresponse.storethemearray.theme', 637 | 'getsuggestedcategoriesresponse.suggestedcategoryarray.suggestedcategory', 638 | 'getuserpreferencesresponse.supportedsellerprofiles.supportedsellerprofile', 639 | 'settaxtableresponse.taxtable.taxjurisdiction', 640 | 'getitemtransactionsresponse.taxes.taxdetails', 641 | 'getordersresponse.taxes.taxdetails', 642 | 'getordertransactionsresponse.taxes.taxdetails', 643 | 'getsellertransactionsresponse.taxes.taxdetails', 644 | 'getdescriptiontemplatesresponse.themegroup.themeid', 645 | 'getuserresponse.topratedsellerdetails.topratedprogram', 646 | 'getordersresponse.transactionarray.transaction', 647 | 'getitemtransactionsresponse.transaction.externaltransaction', 648 | 'getsellertransactionsresponse.transaction.externaltransaction', 649 | 'getebaydetailsresponse.unitofmeasurementdetails.unitofmeasurement', 650 | 'getebaydetailsresponse.unitofmeasurement.alternatetext', 651 | 'getuserpreferencesresponse.unpaiditemassistancepreferences.excludeduser', 652 | 'getsellerlistresponse.useridarray.userid', 653 | 'getuserresponse.user.usersubscription', 654 | 'getuserresponse.user.skypeid', 655 | 'addfixedpriceitemresponse.variationspecificpictureset.pictureurl', 656 | 'revisefixedpriceitemresponse.variationspecificpictureset.pictureurl', 657 | 'relistfixedpriceitemresponse.variationspecificpictureset.pictureurl', 658 | 'verifyaddfixedpriceitemresponse.variationspecificpictureset.pictureurl', 659 | 'getitemresponse.variationspecificpictureset.externalpictureurl', 660 | 'getitemsresponse.variationspecificpictureset.externalpictureurl', 661 | 'addfixedpriceitemresponse.variations.variation', 662 | 'revisefixedpriceitemresponse.variations.variation', 663 | 'relistfixedpriceitemresponse.variations.variation', 664 | 'verifyaddfixedpriceitemresponse.variations.variation', 665 | 'addfixedpriceitemresponse.variations.pictures', 666 | 'revisefixedpriceitemresponse.variations.pictures', 667 | 'relistfixedpriceitemresponse.variations.pictures', 668 | 'verifyaddfixedpriceitemresponse.variations.pictures', 669 | 'getveroreasoncodedetailsresponse.veroreasoncodedetails.verositedetail', 670 | 'veroreportitemsresponse.veroreportitem.region', 671 | 'veroreportitemsresponse.veroreportitem.country', 672 | 'veroreportitemsresponse.veroreportitems.reportitem', 673 | 'getveroreportstatusresponse.veroreporteditemdetails.reporteditem', 674 | 'getveroreasoncodedetailsresponse.verositedetail.reasoncodedetail', 675 | 'getebaydetailsresponse.verifieduserrequirementsdetails.feedbackscore', 676 | 'getwantitnowsearchresultsresponse.wantitnowpostarray.wantitnowpost', 677 | ] 678 | 679 | def build_request_headers(self, verb): 680 | headers = { 681 | "X-EBAY-API-COMPATIBILITY-LEVEL": self.config.get('compatibility', ''), 682 | "X-EBAY-API-DEV-NAME": self.config.get('devid', ''), 683 | "X-EBAY-API-APP-NAME": self.config.get('appid', ''), 684 | "X-EBAY-API-CERT-NAME": self.config.get('certid', ''), 685 | "X-EBAY-API-SITEID": str(self.config.get('siteid', '')), 686 | "X-EBAY-API-CALL-NAME": self.verb, 687 | "Content-Type": "text/xml" 688 | } 689 | if self.config.get('iaf_token', None): 690 | headers["X-EBAY-API-IAF-TOKEN"] = self.config.get('iaf_token') 691 | 692 | return headers 693 | 694 | def build_request_data(self, verb, data, verb_attrs): 695 | xml = "" 696 | xml += "<{verb}Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">".format( 697 | verb=self.verb) 698 | if not self.config.get('iaf_token', None): 699 | xml += "" 700 | if self.config.get('token', None): 701 | xml += "{token}".format( 702 | token=self.config.get('token')) 703 | elif self.config.get('username', None): 704 | xml += "{username}".format( 705 | username=self.config.get('username', '')) 706 | if self.config.get('password', None): 707 | xml += "{password}".format( 708 | password=self.config.get('password', None)) 709 | xml += "" 710 | xml += dict2xml(data, self.escape_xml) 711 | xml += "".format(verb=self.verb) 712 | return xml 713 | 714 | def warnings(self): 715 | warning_string = "" 716 | 717 | if len(self._resp_body_warnings) > 0: 718 | warning_string = "{verb}: {message}" \ 719 | .format(verb=self.verb, message=", ".join(self._resp_body_warnings)) 720 | 721 | return warning_string 722 | 723 | def _get_resp_body_errors(self): 724 | """Parses the response content to pull errors. 725 | 726 | Child classes should override this method based on what the errors in the 727 | XML response body look like. They can choose to look at the 'ack', 728 | 'Errors', 'errorMessage' or whatever other fields the service returns. 729 | the implementation below is the original code that was part of error() 730 | """ 731 | 732 | if self._resp_body_errors and len(self._resp_body_errors) > 0: 733 | return self._resp_body_errors 734 | 735 | errors = [] 736 | warnings = [] 737 | resp_codes = [] 738 | 739 | if self.verb is None: 740 | return errors 741 | 742 | dom = self.response.dom() 743 | if dom is None: 744 | return errors 745 | 746 | for e in dom.findall('Errors'): 747 | eSeverity = None 748 | eClass = None 749 | eShortMsg = None 750 | eLongMsg = None 751 | eCode = None 752 | 753 | try: 754 | eSeverity = e.findall('SeverityCode')[0].text 755 | except IndexError: 756 | pass 757 | 758 | try: 759 | eClass = e.findall('ErrorClassification')[0].text 760 | except IndexError: 761 | pass 762 | 763 | try: 764 | eCode = e.findall('ErrorCode')[0].text 765 | except IndexError: 766 | pass 767 | 768 | try: 769 | eShortMsg = smart_encode(e.findall('ShortMessage')[0].text) 770 | except IndexError: 771 | pass 772 | 773 | try: 774 | eLongMsg = smart_encode(e.findall('LongMessage')[0].text) 775 | except IndexError: 776 | pass 777 | 778 | try: 779 | eCode = e.findall('ErrorCode')[0].text 780 | if int(eCode) not in resp_codes: 781 | resp_codes.append(int(eCode)) 782 | except IndexError: 783 | pass 784 | 785 | msg = str("Class: {eClass}, Severity: {severity}, Code: {code}, {shortMsg} {longMsg}") \ 786 | .format(eClass=eClass, severity=eSeverity, code=eCode, shortMsg=eShortMsg, 787 | longMsg=eLongMsg) 788 | 789 | # from IPython import embed; embed() 790 | 791 | if eSeverity == 'Warning': 792 | warnings.append(msg) 793 | else: 794 | errors.append(msg) 795 | 796 | self._resp_body_warnings = warnings 797 | self._resp_body_errors = errors 798 | self._resp_codes = resp_codes 799 | 800 | if self.config.get('warnings') and len(warnings) > 0: 801 | log.warn("{verb}: {message}\n\n".format( 802 | verb=self.verb, message="\n".join(warnings))) 803 | 804 | if self.response.reply.Ack == 'Failure': 805 | if self.config.get('errors'): 806 | log.error("{verb}: {message}\n\n".format( 807 | verb=self.verb, message="\n".join(errors))) 808 | 809 | return errors 810 | 811 | return [] 812 | 813 | def pages(self): 814 | 815 | tot_pages = 0 816 | epp = self._request_dict.get( 817 | 'Pagination', {}).get('EntriesPerPage', None) 818 | 819 | if not self.response: 820 | resp = self.execute(self.verb, self._request_dict) 821 | tot_pages = int(resp.reply.PaginationResult.TotalNumberOfPages) 822 | yield resp 823 | 824 | for page in range(tot_pages)[1:]: 825 | self._request_dict['Pagination'] = {} 826 | 827 | if epp: 828 | self._request_dict['Pagination']['EntriesPerPage'] = epp 829 | 830 | self._request_dict['Pagination']['PageNumber'] = int(page) + 1 831 | 832 | yield self.execute(self.verb, self._request_dict) 833 | -------------------------------------------------------------------------------- /ebaysdk/trading/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/ebaysdk/trading/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /ebaysdk/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | ''' 5 | © 2012-2013 eBay Software Foundation 6 | Authored by: Tim Keefer 7 | Licensed under CDDL 1.0 8 | ''' 9 | import sys 10 | from lxml import etree as ET 11 | from xml.sax.saxutils import escape 12 | 13 | if sys.version_info[0] >= 3: 14 | unicode = str 15 | long = int 16 | 17 | 18 | def parse_yaml(yaml_file): 19 | """ 20 | This is simple approach to parsing a yaml config that is only 21 | intended for this SDK as this only supports a very minimal subset 22 | of yaml options. 23 | """ 24 | 25 | with open(yaml_file) as f: 26 | data = {None: {}} 27 | current_key = None 28 | 29 | for line in f.readlines(): 30 | 31 | # ignore comments 32 | if line.startswith('#'): 33 | continue 34 | 35 | # parse the header 36 | elif line[0].isalnum(): 37 | key = line.strip().replace(':', '') 38 | current_key = key 39 | data[current_key] = {} 40 | 41 | # parse the key: value line 42 | elif line[0].isspace(): 43 | values = line.strip().split(':', 1) 44 | 45 | if len(values) == 2: 46 | cval = values[1].strip() 47 | 48 | if cval == '0': 49 | cval = False 50 | elif cval == '1': 51 | cval = True 52 | 53 | data[current_key][values[0].strip()] = cval 54 | 55 | return data 56 | 57 | 58 | def python_2_unicode_compatible(klass): 59 | """ 60 | A decorator that defines __unicode__ and __str__ methods under Python 2. 61 | Under Python 3 it does nothing. 62 | 63 | To support Python 2 and 3 with a single code base, define a __str__ method 64 | returning text and apply this decorator to the class. 65 | """ 66 | if sys.version_info[0] < 3: 67 | if '__str__' not in klass.__dict__: 68 | raise ValueError("@python_2_unicode_compatible cannot be applied " 69 | "to %s because it doesn't define __str__()." % 70 | klass.__name__) 71 | klass.__unicode__ = klass.__str__ 72 | klass.__str__ = lambda self: self.__unicode__().encode('utf-8') 73 | return klass 74 | 75 | 76 | def get_dom_tree(xml): 77 | tree = ET.fromstring(xml) # pylint: disable=no-member 78 | return tree.getroottree().getroot() 79 | 80 | 81 | def attribute_check(root): 82 | attrs = [] 83 | value = None 84 | 85 | if isinstance(root, dict): 86 | if '#text' in root: 87 | value = root['#text'] 88 | if '@attrs' in root: 89 | for ak, av in sorted(root.pop('@attrs').items()): 90 | attrs.append(str('{0}="{1}"').format(ak, smart_encode(av))) 91 | 92 | return attrs, value 93 | 94 | 95 | def smart_encode_request_data(value): 96 | try: 97 | if sys.version_info[0] < 3: 98 | return value 99 | 100 | if isinstance(value, str): 101 | return value.encode('utf-8') 102 | else: 103 | return value 104 | 105 | except UnicodeDecodeError as e: 106 | return value 107 | 108 | 109 | def smart_encode(value): 110 | try: 111 | if sys.version_info[0] < 3: 112 | return unicode(value).encode('utf-8') # pylint: disable-msg=E0602 113 | else: 114 | return value 115 | # return str(value) 116 | 117 | except UnicodeDecodeError: 118 | return value 119 | 120 | 121 | def smart_decode(str): 122 | try: 123 | if sys.version_info[0] < 3: 124 | return str.decode('utf-8') 125 | return str 126 | except UnicodeEncodeError: 127 | return str 128 | 129 | 130 | def to_xml(root): 131 | return dict2xml(root) 132 | 133 | 134 | def dict2xml(root, escape_xml=False): 135 | ''' 136 | Doctests: 137 | >>> dict1 = {'Items': {'ItemId': ['1234', '2222']}} 138 | >>> dict2xml(dict1) 139 | '12342222' 140 | >>> dict2 = { 141 | ... 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'} }}, 142 | ... 'paginationInput': { 143 | ... 'pageNumber': '1', 144 | ... 'pageSize': '25' 145 | ... }, 146 | ... 'sortOrder': 'StartTimeNewest' 147 | ... } 148 | >>> dict2xml(dict2) 149 | '125222StartTimeNewest' 150 | >>> dict3 = { 151 | ... 'parent': {'child': {'#text': 222, '@attrs': {'site': 'US', 'id': 1234}}} 152 | ... } 153 | >>> dict2xml(dict3) 154 | '222' 155 | >>> dict5 = { 156 | ... 'parent': {'child': {'@attrs': {'site': 'US', 'id': 1234}, }} 157 | ... } 158 | >>> dict2xml(dict5) 159 | '' 160 | >>> dict4 = { 161 | ... 'searchFilter': {'categoryId': {'#text': 0, '@attrs': {'site': 'US'} }}, 162 | ... 'paginationInput': { 163 | ... 'pageNumber': '1', 164 | ... 'pageSize': '25' 165 | ... }, 166 | ... 'itemFilter': [ 167 | ... {'name': 'Condition', 168 | ... 'value': 'Used'}, 169 | ... {'name': 'LocatedIn', 170 | ... 'value': 'GB'}, 171 | ... ], 172 | ... 'sortOrder': 'StartTimeNewest' 173 | ... } 174 | >>> dict2xml(dict4) 175 | 'ConditionUsedLocatedInGB1250StartTimeNewest' 176 | >>> dict2xml({}) 177 | '' 178 | >>> dict2xml('b') 179 | 'b' 180 | >>> dict2xml(None) 181 | '' 182 | >>> common_attrs = {'xmlns:xs': 'http://www.w3.org/2001/XMLSchema', 'xsi:type': 'xs:string'} 183 | >>> attrdict = { 'attributeAssertion': [ 184 | ... {'@attrs': {'Name': 'DevId', 'NameFormat': 'String', 'FriendlyName': 'DeveloperID'}, 185 | ... 'urn:AttributeValue': { 186 | ... '@attrs': common_attrs, 187 | ... '#text': 'mydevid' 188 | ... }, 189 | ... }, 190 | ... {'@attrs': {'Name': 'AppId', 'NameFormat': 'String', 'FriendlyName': 'ApplicationID'}, 191 | ... 'urn:AttributeValue': { 192 | ... '@attrs': common_attrs, 193 | ... '#text': 'myappid', 194 | ... }, 195 | ... }, 196 | ... {'@attrs': {'Name': 'CertId', 'NameFormat': 'String', 'FriendlyName': 'Certificate'}, 197 | ... 'urn:AttributeValue': { 198 | ... '@attrs': common_attrs, 199 | ... '#text': 'mycertid', 200 | ... }, 201 | ... }, 202 | ... ], 203 | ... } 204 | >>> print(dict2xml(attrdict)) 205 | mydevidmyappidmycertid 206 | 207 | >>> dict2xml("łśżźć") # doctest: +SKIP 208 | '\\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87' 209 | 210 | >>> dict_special = { 211 | ... 'searchFilter': {'categoryId': {'#text': 'SomeID - łśżźć', '@attrs': {'site': 'US - łśżźć'} }}, 212 | ... 'paginationInput': { 213 | ... 'pageNumber': '1 - łśżźć', 214 | ... 'pageSize': '25 - łśżźć' 215 | ... }, 216 | ... 'itemFilter': [ 217 | ... {'name': 'Condition - łśżźć', 218 | ... 'value': 'Used - łśżźć'}, 219 | ... {'name': 'LocatedIn - łśżźć', 220 | ... 'value': 'GB - łśżźć'}, 221 | ... ], 222 | ... 'sortOrder': 'StartTimeNewest - łśżźć' 223 | ... } 224 | >>> dict2xml(dict_special) # doctest: +SKIP 225 | 'Condition - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87Used - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87LocatedIn - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87GB - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x871 - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x8725 - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87SomeID - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87StartTimeNewest - \\xc5\\x82\\xc5\\x9b\\xc5\\xbc\\xc5\\xba\\xc4\\x87' 226 | ''' 227 | 228 | xml = str('') 229 | if root is None: 230 | return xml 231 | 232 | if isinstance(root, dict): 233 | for key in sorted(root.keys()): 234 | 235 | if isinstance(root[key], dict): 236 | attrs, value = attribute_check(root[key]) 237 | 238 | if value is None: 239 | value = dict2xml(root[key], escape_xml) 240 | elif isinstance(value, dict): 241 | value = dict2xml(value, escape_xml) 242 | 243 | attrs_sp = str('') 244 | if len(attrs) > 0: 245 | attrs_sp = str(' ') 246 | 247 | xml = str('{xml}<{tag}{attrs_sp}{attrs}>{value}') \ 248 | .format(**{'tag': key, 'xml': str(xml), 'attrs': str(' ').join(attrs), 249 | 'value': smart_encode(value), 'attrs_sp': attrs_sp}) 250 | 251 | elif isinstance(root[key], list): 252 | 253 | for item in root[key]: 254 | attrs, value = attribute_check(item) 255 | 256 | if value is None: 257 | value = dict2xml(item, escape_xml) 258 | elif isinstance(value, dict): 259 | value = dict2xml(value, escape_xml) 260 | 261 | attrs_sp = '' 262 | if len(attrs) > 0: 263 | attrs_sp = ' ' 264 | 265 | xml = str('{xml}<{tag}{attrs_sp}{attrs}>{value}') \ 266 | .format(**{'xml': str(xml), 'tag': key, 'attrs': ' '.join(attrs), 'value': smart_encode(value), 267 | 'attrs_sp': attrs_sp}) 268 | 269 | else: 270 | value = root[key] 271 | if escape_xml and hasattr(value, 'startswith') and not value.startswith('{value}') \ 274 | .format(**{'xml': str(xml), 'tag': key, 'value': smart_encode(value)}) 275 | 276 | elif isinstance(root, str) or isinstance(root, int) \ 277 | or isinstance(root, float) or isinstance(root, long) \ 278 | or isinstance(root, unicode): 279 | xml = str('{0}{1}').format(str(xml), smart_encode(root)) 280 | else: 281 | raise Exception('Unable to serialize node of type %s (%s)' % 282 | (type(root), root)) 283 | 284 | return xml 285 | 286 | 287 | def getValue(response_dict, *args, **kwargs): 288 | args_a = [w for w in args] 289 | first = args_a[0] 290 | args_a.remove(first) 291 | 292 | h = kwargs.get('mydict', {}) 293 | if h: 294 | h = h.get(first, {}) 295 | else: 296 | h = response_dict.get(first, {}) 297 | 298 | if len(args) == 1: 299 | try: 300 | return h.get('value', None) 301 | except Exception as e: 302 | return h 303 | 304 | last = args_a.pop() 305 | 306 | for a in args_a: 307 | h = h.get(a, {}) 308 | 309 | h = h.get(last, {}) 310 | 311 | try: 312 | return h.get('value', None) 313 | except Exception as e: 314 | return h 315 | 316 | 317 | def getNodeText(node): 318 | "Returns the node's text string." 319 | 320 | rc = [] 321 | 322 | if hasattr(node, 'childNodes'): 323 | for cn in node.childNodes: 324 | if cn.nodeType == cn.TEXT_NODE: 325 | rc.append(cn.data) 326 | elif cn.nodeType == cn.CDATA_SECTION_NODE: 327 | rc.append(cn.data) 328 | 329 | return ''.join(rc) 330 | 331 | 332 | def perftest_dict2xml(): 333 | sample_dict = { 334 | 'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'}}}, 335 | 'paginationInput': { 336 | 'pageNumber': '1', 337 | 'pageSize': '25' 338 | }, 339 | 'itemFilter': [ 340 | {'name': 'Condition', 341 | 'value': 'Used'}, 342 | {'name': 'LocatedIn', 343 | 'value': 'GB'}, 344 | ], 345 | 'sortOrder': 'StartTimeNewest' 346 | } 347 | 348 | xml = dict2xml(sample_dict) 349 | 350 | 351 | if __name__ == '__main__': 352 | 353 | import timeit 354 | print("perftest_dict2xml() %s" % 355 | timeit.timeit("perftest_dict2xml()", number=50000, 356 | setup="from __main__ import perftest_dict2xml")) 357 | 358 | import doctest 359 | failure_count, test_count = doctest.testmod() 360 | sys.exit(failure_count) 361 | -------------------------------------------------------------------------------- /findProducts.py: -------------------------------------------------------------------------------- 1 | from AliProduct import AliProduct 2 | from time import sleep 3 | from selenium import * 4 | from selenium import webdriver 5 | from selenium.webdriver.chrome.options import Options 6 | from selenium.webdriver.common.actions.interaction import KEY 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.common.keys import Keys 9 | from selenium.webdriver.support import expected_conditions as EC 10 | from selenium.webdriver.support.ui import WebDriverWait 11 | from bs4 import BeautifulSoup 12 | import requests 13 | from getProductInfo import proxiedAliLogin 14 | from firebase import firebase 15 | import common 16 | import urllib.parse 17 | 18 | class ebayResult: 19 | def __init__(self, numSold, avgPrice): 20 | self.numSold = numSold 21 | self.avgPrice = avgPrice 22 | 23 | 24 | def ebaySoldResults(query): 25 | r = requests.get('https://www.ebay.com/sch/i.html?_from=R40&_nkw=' + query + '&_sacat=0&LH_TitleDesc=0&rt=nc&LH_Sold=1&LH_Complete=1') 26 | soup = BeautifulSoup(r.text, 'html.parser') 27 | numSold = int(soup.select('.srp-controls__count-heading')[0].decode_contents().split()[0].replace(',','')) 28 | avgPrice = 0.0 29 | if numSold > 0: 30 | totalPrice = 0 31 | prices = soup.select('.POSITIVE') 32 | for price in prices: 33 | totalPrice += float(price.decode_contents().replace('$', '')) 34 | avgPrice = totalPrice / len(prices) 35 | return ebayResult(numSold, avgPrice) 36 | 37 | def getPageURL(baseURL, queryTerm, pageNumber): 38 | init_id_loc = baseURL.index('&initiative_id') 39 | initiative_id = baseURL[init_id_loc + 14: baseURL.index('&', init_id_loc)] 40 | return urllib.parse.quote('https://www.aliexpress.com/wholesale?site=glo&g=y&SortType=price_asc&SearchText=' + queryTerm + '&groupsort=1&page=' + pageNumber + '&initiative_id=' + initiative_id + '&needQuery=n&isFreeShip=y') 41 | 42 | 43 | 44 | def aliQuery(queryTerm): 45 | pLinkList = [] 46 | driver, proxy, proxyDict = proxiedAliLogin() 47 | #driver.get("https://www.aliexpress.com") 48 | 49 | common.waitAndSendKeysCSS('#search-key', 30, driver, queryTerm) 50 | common.waitAndClickCSS('.search-button', 10, driver) 51 | # common.waitAndSendKeysCSS('#fm-login-id', 5, driver, proxyDict[proxy]['username']) 52 | try: 53 | common.waitAndSendKeysCSS('#fm-login-password', 10, driver, proxyDict[proxy]['password']) 54 | common.waitAndClickCSS('#login-form > div.fm-btn > button', 10, driver) 55 | except: 56 | print('no login prompted') 57 | 58 | driver.fullscreen_window() 59 | common.waitAndClickCSS('#price_lowest_1', 5, driver) 60 | common.waitAndClickCSS('#linkFreeShip .check-icon', 5, driver) 61 | searchResultsCount = int(common.waitAndTextCSS('.search-count', 5, driver).replace(',','')) 62 | if (searchResultsCount < 1): 63 | print('no results :(') 64 | return pLinkList 65 | totalPages = searchResultsCount / 20 66 | sleep(5) 67 | baseURL = driver.current_url 68 | nextPageNum = 1 69 | while nextPageNum < round(totalPages * 0.5): 70 | if (nextPageNum > 1): 71 | driver.get(getPageURL(baseURL, queryTerm, nextPageNum)) 72 | sleep(5) 73 | html = driver.page_source 74 | soup = BeautifulSoup(html, 'html.parser') 75 | productList = soup.select('.product') 76 | for product in productList: 77 | pLinkList.append(product['href']) 78 | print(pLinkList) 79 | return pLinkList 80 | 81 | aliQuery('hammock') -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /getProductInfo.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Jun 9, 2019 3 | 4 | @author: Bgray 5 | ''' 6 | from AliProduct import AliProduct 7 | from time import sleep 8 | from selenium import * 9 | from selenium import webdriver 10 | from selenium.webdriver.chrome.options import Options 11 | from selenium.webdriver.common.actions.interaction import KEY 12 | from selenium.webdriver.common.by import By 13 | from selenium.webdriver.common.keys import Keys 14 | from selenium.webdriver.support import expected_conditions as EC 15 | from selenium.webdriver.support.ui import WebDriverWait 16 | import threading 17 | import requests 18 | import os 19 | import random 20 | import jsonpickle 21 | import string 22 | import pickle 23 | import time 24 | from datetime import datetime 25 | import json 26 | from bs4 import BeautifulSoup 27 | from firebase import firebase 28 | firebase = firebase.FirebaseApplication('https://proxypool-f3996.firebaseio.com/', None) 29 | proxyDict = {} 30 | proxyCookieDict = {} 31 | 32 | def getProxiesFromDB(): 33 | try: 34 | resul = firebase.get('/proxies/value/', None) 35 | if (resul is not None): 36 | proxyDict.update(json.loads(resul)['data']) 37 | cookieResul = firebase.get('/proxies/cookies', None) 38 | if (cookieResul is not None): 39 | proxyCookieDict.update(jsonpickle.decode(cookieResul)['data']) 40 | print(proxyCookieDict) 41 | except: 42 | print('I/O error. Retrying...', flush=True) 43 | getProxiesFromDB() 44 | 45 | def postProxiesToDB(): 46 | proxyData = {'data': proxyDict} 47 | proxyCookieData = {'data': proxyCookieDict} 48 | try: 49 | firebase.put('/proxies/', 'value', json.dumps(proxyData)) 50 | firebase.put('/proxies/', 'cookies', jsonpickle.encode(proxyCookieData)) 51 | except: 52 | print('I/O error. Retrying...', flush=True) 53 | postProxiesToDB() 54 | 55 | 56 | options = webdriver.ChromeOptions() 57 | 58 | 59 | def scrapeProxyLists(): 60 | r = requests.get("https://www.us-proxy.org/") 61 | soup = BeautifulSoup(r.text, 'html.parser') 62 | rows = soup.find_all('tr') 63 | for ind, row in enumerate(rows): 64 | try: 65 | rowSoup = BeautifulSoup(str(row), 'html.parser') 66 | tds = rowSoup.find_all('td') 67 | if (tds[6].decode_contents() == 'yes'): 68 | proxy = tds[0].decode_contents() + ":" + tds[1].decode_contents() 69 | if proxy not in proxyDict: 70 | proxyDict[proxy] = {'failures': 0, 'username': '', 'password': ''} 71 | proxies.append(proxy) 72 | # else: 73 | # print('skipping %s:%s since no https' % (tds[0].decode_contents(), tds[1].decode_contents())) 74 | except: 75 | pass 76 | # print("skipping row %d" % ind) 77 | 78 | def saveProxies(): 79 | with open("proxyList.txt", "w+") as f: 80 | for proxy in proxyDict.values(): 81 | f.write("%s\n" % proxy) 82 | 83 | 84 | def getRandProxy(): 85 | #return proxies[random.randrange(0, len(proxies))] 86 | return '1.1.1.1:3300' 87 | 88 | proxy = getRandProxy() 89 | options.add_argument("--headless") 90 | options.add_argument("--log-level=3") 91 | threadList = [] 92 | 93 | 94 | def getRandomString(stringLength): 95 | letters = string.ascii_lowercase 96 | return ''.join(random.choice(letters) for i in range(stringLength)) 97 | 98 | 99 | def proxiedAliLogin(): 100 | myProxy = '' 101 | getProxiesFromDB() 102 | scrapeProxyLists() 103 | print(proxyDict) 104 | print(proxyCookieDict) 105 | successfulProxy = False 106 | for proxy in list(proxyCookieDict.keys()) + list(proxyDict.keys()): 107 | print(proxy) 108 | if successfulProxy: 109 | break 110 | myProxy = proxy 111 | options.add_argument('--proxy-server=%s' % proxy) 112 | driver = webdriver.Chrome(chrome_options=options,executable_path="chromedriver.exe") 113 | driver.get("https://www.aliexpress.com") 114 | print("starting proxy: %s" % proxy) 115 | try: 116 | waitAndClickCSS('body > div.ui-window.ui-window-normal.ui-window-transition.ui-newuser-layer-dialog > div > div > a', 5, driver) 117 | except: 118 | print('no popup') 119 | if proxy not in proxyCookieDict.keys(): 120 | print("login first!") 121 | emailName = '' 122 | passwordStr = '' 123 | try: 124 | waitAndClickCSS('span.join-btn > a', 60, driver) 125 | emailName = getRandomString(12) 126 | waitAndSendKeys('//*[@id="ws-xman-register-email"]', 30, driver, '%s@gmail.com' % emailName) 127 | passwordStr = getRandomString(10) 128 | waitAndSendKeys('//*[@id="ws-xman-register-password"]', 30, driver, passwordStr) 129 | waitAndClickCSS('#ws-xman-register-submit', 30, driver) 130 | sleep(10) 131 | except: 132 | print('error with proxy: %s' % proxy) 133 | proxyDict[proxy]['failures'] += 1 134 | driver.quit() 135 | continue 136 | proxyDict[proxy]['username'] = emailName 137 | proxyDict[proxy]['password'] = passwordStr 138 | proxyCookieDict[proxy] = pickle.dumps(driver.get_cookies()) 139 | # try: 140 | # waitAndTextCSS('body > div.ui-window.ui-window-normal.ui-window-transition.ui-newuser-layer-dialog > div > div > a', 5, driver) 141 | # except: 142 | # proxyDict[proxy]['failures'] += 1 143 | # driver.quit() 144 | # continue 145 | successfulProxy = True 146 | for c in pickle.loads(proxyCookieDict[proxy]): 147 | try: 148 | driver.add_cookie(c) 149 | print('working cookie!') 150 | except: 151 | print('error with proxy: %s' % proxy) 152 | proxyDict[proxy]['failures'] += 1 153 | driver.quit() 154 | successfulProxy = False 155 | break 156 | if not successfulProxy: 157 | continue 158 | postProxiesToDB() 159 | 160 | return (driver, myProxy, proxyDict) 161 | 162 | 163 | 164 | def getAliProdInfo(url, driver=None): 165 | if driver is None: 166 | driver = webdriver.Chrome(chrome_options=options,executable_path='chromedriver.exe') 167 | prodDeliveryEstimate = "undefined" 168 | prodNumOrders = -1 169 | prodReviewScore = 0.0 170 | prodReviewNum = 0 171 | colorInfo = {} 172 | itemSpecs = {} 173 | urlFilenameDict = {} 174 | itemSpecs['ItemSpecifics'] = {} 175 | itemSpecs['ItemSpecifics']['NameValueList'] = [] 176 | hasColors = False 177 | preferredColor = {} 178 | today = datetime.today() 179 | driver.get(url) 180 | try: 181 | waitAndClickCSS('body > div.next-overlay-wrapper.opened > div.next-overlay-inner.next-dialog-container > div > a', 5, driver) 182 | except: 183 | print('no popup') 184 | shippingOpts = driver.find_elements_by_css_selector('#root > div > div.product-main > div > div.product-info > div.product-sku > div > div:nth-child(2) > ul > li > div') 185 | if (len(shippingOpts) > 1): 186 | waitAndClickCSS('#root > div > div.product-main > div > div.product-info > div.product-sku > div > div:nth-child(2) > ul > li > div > span', 30, driver) 187 | elemList = driver.find_elements_by_css_selector('#j-image-thumb-list span img') 188 | if (len(elemList) == 0): 189 | elemList = driver.find_elements_by_css_selector('#root > div > div.product-main > div > div.img-view-wrap > div > div > div.images-view-wrap > ul div img') 190 | fURL = elemList[0].get_attribute('src') 191 | imageDir = fURL[57:fURL.index('.jpg')] 192 | 193 | #prodName = waitAndTextCSS('.product-name', 5, driver) 194 | prodName = waitAndTextCSS('.product-title', 5, driver) 195 | 196 | #prodPrice = waitAndTextCSS('#j-sku-price', 30, driver) 197 | prodPrice = getPriceFromString(waitAndTextCSS('.product-price-value', 5, driver)) 198 | 199 | #prodShippingPrice = waitAndTextCSS('.logistics-cost', 30, driver) 200 | prodShippingPrice = waitAndTextCSS('.product-shipping-price', 5, driver) 201 | 202 | #prodShippingService = waitAndTextCSS('.shipping-link', 30, driver) 203 | prodShippingService = waitAndTextCSS('.product-shipping-info', 5, driver) 204 | 205 | try: 206 | #prodReviewScore = waitAndTextCSS('.percent-num', 30, driver) 207 | prodReviewScore = waitAndTextCSS('.overview-rating-average', 5, driver) 208 | 209 | #prodReviewNum = waitAndTextCSS('.rantings-num', 30, driver) 210 | prodReviewNum = waitAndTextCSS('.product-reviewer-reviews', 5, driver) 211 | if (len(prodReviewNum) > 0): 212 | prodReviewNum = prodReviewNum.split()[0][1:] 213 | except: 214 | print('no reviews') 215 | 216 | try: 217 | os.mkdir(imageDir) 218 | except: 219 | print("already exists") 220 | 221 | 222 | colorOpts = driver.find_elements_by_css_selector('.sku-title') 223 | if (len(colorOpts) > 0): 224 | runningTotalPrice = 0.0 225 | numColors = 0 226 | hasColors = True 227 | waitAndClickCSS('.sku-property-image img', 5, driver) 228 | colorName = waitAndTextCSS('.sku-title-value', 5, driver) 229 | itemSpecs['Color'] = colorName 230 | colorChoices = driver.find_elements_by_css_selector('.sku-property-image img') 231 | print(len(colorChoices)) 232 | for ind, color in enumerate(colorChoices): 233 | if (ind != 0): 234 | color.click() 235 | thisColorName = waitAndTextCSS('.sku-title-value', 5, driver) 236 | 237 | thisColorPrice = waitAndTextCSS('.product-price-value', 5, driver) 238 | tipVal = waitAndTextCSS('.product-quantity-tip span',5,driver).split()[0] 239 | if (tipVal == 'Only'): 240 | continue 241 | colorInfo[thisColorName] = {} 242 | colorInfo[thisColorName]['numLeft'] = int(waitAndTextCSS('.product-quantity-tip span',5,driver).split()[0]) 243 | rawURL = driver.find_element_by_css_selector('#root > div > div.product-main > div > div.img-view-wrap > div > div > div.image-view-magnifier-wrap > img').get_attribute('src') 244 | filename = imageDir + "/" + prodName + "-" + thisColorName 245 | savePicToFile(rawURL, filename) 246 | colorInfo[thisColorName]['thisColorImg'] = filename 247 | colorInfo[thisColorName]['price'] = getPriceFromString(thisColorPrice) 248 | runningTotalPrice += colorInfo[thisColorName]['price'] 249 | numColors += 1 250 | 251 | cheapest_color_choice = min(colorInfo.keys(), key=(lambda k: colorInfo[k]['price'])) 252 | print(cheapest_color_choice) 253 | if (colorInfo[cheapest_color_choice]['price'] < 0.9 * (runningTotalPrice/numColors)): 254 | del colorInfo[cheapest_color_choice] 255 | most_popular_color_choice = min(colorInfo.keys(), key=(lambda k: colorInfo[k]['numLeft'])) 256 | preferredColor = colorInfo[most_popular_color_choice] 257 | preferredColor['colorName'] = most_popular_color_choice 258 | 259 | 260 | prodDeliveryEstimate = waitAndTextCSS('.product-shipping-delivery span',5,driver) 261 | prodDeliveryEstimate = list(map(int, prodDeliveryEstimate.split('/'))) 262 | if prodDeliveryEstimate[0] < today.month: 263 | deliveryDate = datetime(today.year + 1, prodDeliveryEstimate[0], prodDeliveryEstimate[0]) 264 | else: 265 | deliveryDate = datetime(today.year, prodDeliveryEstimate[0], prodDeliveryEstimate[0]) 266 | prodMaxDeliveryTimeDays = (deliveryDate - today).days 267 | prodMinDeliveryTimeDays = prodMaxDeliveryTimeDays - 7 268 | try: 269 | #prodNumOrders = waitAndTextCSS('#j-order-num', 30, driver) 270 | prodNumOrders = waitAndTextCSS('.product-reviewer-sold', 5, driver) 271 | if (len(prodNumOrders) > 0): 272 | prodNumOrders = prodNumOrders.split()[0] 273 | except: 274 | print('no prod num orders') 275 | 276 | 277 | 278 | images = set() 279 | for ind, e in enumerate(elemList): 280 | imageURL = e.get_attribute('src') 281 | images.add(imageDir + "/" + imageDir.split('-')[0] + '-' + str(ind) + ".jpg") 282 | savePicToFile(imageURL[:imageURL.index('_50')], imageDir + "/" + imageDir.split('-')[0] + '-' + str(ind) + ".jpg") 283 | actionChain = webdriver.ActionChains(driver) 284 | actionChain.send_keys(Keys.SPACE).perform() 285 | waitAndClickCSS('#product-detail > div.product-detail-tab > div > div.detail-tab-bar > ul > li:nth-child(3) > div > span', 10, driver) 286 | 287 | itemSpecsElems = driver.find_elements_by_css_selector('.product-prop') 288 | if (len(itemSpecsElems) == 0): 289 | print("no specs") 290 | else: 291 | for itemElem in itemSpecsElems: 292 | title = itemElem.find_element_by_css_selector('.property-title').text 293 | title = title[:title.index(':')].replace('&', '') 294 | if (title not in itemSpecs): 295 | itemSpecs[title] = itemElem.find_element_by_css_selector('.property-desc').text 296 | itemSpecs['ItemSpecifics']['NameValueList'].append({"Name": title, "Value": itemElem.find_element_by_css_selector('.property-desc').text.replace('&', '') }) 297 | 298 | driver.quit() 299 | return AliProduct(prodName, hasColors, images, imageDir, prodPrice, prodShippingPrice, prodShippingService, prodDeliveryEstimate, prodMaxDeliveryTimeDays, prodMinDeliveryTimeDays, prodReviewScore, prodReviewNum, prodNumOrders, itemSpecs['ItemSpecifics'], colorInfo, preferredColor) 300 | 301 | 302 | 303 | 304 | def getPriceFromString(priceStr): 305 | priceNameList = priceStr.strip().split() 306 | return float(priceNameList[len(priceNameList) - 1].replace('$','')) 307 | 308 | def waitAndClickCSS(css_selector, timeout, driver): 309 | try: 310 | element = WebDriverWait(driver, timeout).until( 311 | EC.presence_of_element_located((By.CSS_SELECTOR, css_selector)) 312 | ) 313 | finally: 314 | element.click() 315 | 316 | def waitAndClick(xPath, timeout, driver): 317 | try: 318 | element = WebDriverWait(driver, timeout).until( 319 | EC.presence_of_element_located((By.XPATH, xPath)) 320 | ) 321 | finally: 322 | element.click() 323 | 324 | def waitAndSendKeys(xPath, timeout, driver, keysToSend): 325 | try: 326 | element = WebDriverWait(driver, timeout).until( 327 | EC.presence_of_element_located((By.XPATH, xPath)) 328 | ) 329 | finally: 330 | element.send_keys(keysToSend) 331 | 332 | def waitAndTextCSS(css_selector, timeout, driver): 333 | try: 334 | element = WebDriverWait(driver, timeout).until( 335 | EC.presence_of_element_located((By.CSS_SELECTOR, css_selector)) 336 | ) 337 | finally: 338 | return element.text 339 | 340 | def savePicToFile(picURL, filename): 341 | pic_r = requests.get(picURL) 342 | if (pic_r.status_code != 200): 343 | print("error: picture does not exist!") 344 | return -1 345 | with open(filename, "wb+") as f: 346 | f.write(pic_r.content) 347 | 348 | # for proxy in proxies: 349 | # threadList.append(threading.Thread(target=getAliProdInfo, args = (proxy, 'https://www.aliexpress.com/item/32890025580.html?spm=a2g0o.productlist.0.0.7cda67bdNmwO33&algo_pvid=0725aed4-e4a2-4530-b795-5e09456ae942&algo_expid=0725aed4-e4a2-4530-b795-5e09456ae942-12&btsid=182cff63-a588-48e6-96ec-151b65dd82cc&ws_ab_test=searchweb0_0%2Csearchweb201602_4%2Csearchweb201603_52'))) 350 | # threadList.pop().start() 351 | 352 | # if __name__ == '__main__': 353 | # getProxiesFromDB() 354 | # scrapeProxyLists() 355 | # postProxiesToDB() 356 | # driver = webdriver.Chrome(chrome_options=options,executable_path="chromedriver.exe") 357 | 358 | #driver.get('https://www.aliexpress.com') 359 | #getAliProdInfo(driver, 'https://www.aliexpress.com/item/Luxury-Women-Watches-Magnetic-Starry-Sky-Female-Clock-Quartz-Wristwatch-Fashion-Ladies-Wrist-Watch-reloj-mujer/32968577325.html?spm=2114.search0104.3.1.3fc967bd4Dqp18&ws_ab_test=searchweb0_0,searchweb201602_3_10065_10130_10068_10890_10547_319_10546_317_10548_10545_10696_453_10084_454_10083_10618_10307_537_536_10059_10884_10887_321_322_10103,searchweb201603_53,ppcSwitch_0&algo_expid=40a38e8b-dda1-4af1-a4d0-fc00d4ec1897-0&algo_pvid=40a38e8b-dda1-4af1-a4d0-fc00d4ec1897') 360 | #p = getAliProdInfo(driver, 'https://www.aliexpress.com/item/Men-Sport-LED-Watches-Men-s-Digital-Watch-Men-Watch-Silicone-Electronic-Watch-Men-Clock-reloj/32914540611.html?spm=2114.search0104.3.9.165467bdwzCFFF&ws_ab_test=searchweb0_0,searchweb201602_3_10065_10130_10068_10547_319_10546_317_10548_10545_10696_10084_453_454_10083_10618_10307_537_536_10059_10884_10887_321_322_10103,searchweb201603_53,ppcSwitch_0&algo_expid=7390a65f-2daa-4ddb-825e-26130f84a936-1&algo_pvid=7390a65f-2daa-4ddb-825e-26130f84a936') 361 | #p = getAliProdInfo(driver, 'https://www.aliexpress.com/item/33016438300.html?spm=2114.search0104.3.36.298367bdBLy6pE&ws_ab_test=searchweb0_0,searchweb201602_3_10065_10130_10068_10547_319_10546_317_10548_10545_10696_10084_453_454_10083_10618_10307_537_536_10059_10884_10887_321_322_10103,searchweb201603_53,ppcSwitch_0&algo_expid=b7430a35-c2e7-416c-8cba-67336d7c7ad9-4&algo_pvid=b7430a35-c2e7-416c-8cba-67336d7c7ad9') 362 | # p = getAliProdInfo('https://www.aliexpress.com/item/32889675086.html?spm=2114.search0104.3.121.500667bd65JdeT&ws_ab_test=searchweb0_0%2Csearchweb201602_3_10065_10130_10068_10547_319_10546_317_10548_10545_10696_10084_453_454_10083_10618_10307_537_536_10059_10884_10887_321_322_10103%2Csearchweb201603_53%2CppcSwitch_0&algo_expid=f7517f6c-ad71-4b08-bb8a-a0b06501c97d-15&algo_pvid=f7517f6c-ad71-4b08-bb8a-a0b06501c97d') 363 | # p.printProduct() -------------------------------------------------------------------------------- /itemTemplate.json: -------------------------------------------------------------------------------- 1 | {"Item": { 2 | "AutoPay": "false", 3 | "BestOfferDetails": { 4 | "BestOfferEnabled": "true" 5 | }, 6 | "Description": "This is the first book in the Harry Potter series. In excellent condition!", 7 | "BuyerResponsibleForShipping": "false", 8 | "BuyItNowPrice": "0.0", 9 | "Country": "CN", 10 | "Currency": "USD", 11 | "ListingDetails": { 12 | "MinimumBestOfferPrice": "6.0" 13 | }, 14 | "ListingDuration": "GTC", 15 | "ListingType": "FixedPriceItem", 16 | "Location": "Shanghai", 17 | "PaymentMethods": "PayPal", 18 | "PayPalEmailAddress": "YOUR_EMAIL_HERE@gmail.com", 19 | "PrimaryCategory": { 20 | "CategoryID": "31387" 21 | }, 22 | "PrivateListing": "false", 23 | "Quantity": "1", 24 | "ReservePrice": "0.0", 25 | "SellerProfiles": { 26 | "SellerPaymentProfile": { 27 | "PaymentProfileName": "PayPal:Immediate pay" 28 | }, 29 | "SellerReturnProfile": { 30 | "ReturnProfileName": "30 Day Return Policy" 31 | }, 32 | "SellerShippingProfile": { 33 | "ShippingProfileName": "China Post Ordinary Small Packet Plus" 34 | } 35 | }, 36 | "ShippingDetails": { 37 | "SalesTax": { 38 | "SalesTaxPercent": "0.0", 39 | "ShippingIncludedInTax": "false" 40 | }, 41 | "ShippingServiceOptions": { 42 | "ShippingService": "US_StandardShippingFromGC", 43 | "ShippingServiceCost": "0.0", 44 | "ShippingServicePriority": "1", 45 | "ExpeditedService": "false", 46 | "ShippingTimeMin": "7", 47 | "ShippingTimeMax": "19", 48 | "FreeShipping": "true" 49 | }, 50 | "ShippingType": "Flat", 51 | "ShippingDiscountProfileID": "0", 52 | "InternationalShippingDiscountProfileID": "0" 53 | }, 54 | "ShipToLocations": "US", 55 | "Site": "US", 56 | "StartPrice": "11.99", 57 | "Title": "Fashion SOKI Mens Date Stainless Steel Leather Analog Quartz Sport Wrist Watch", 58 | 59 | 60 | "PictureDetails": { 61 | "PhotoDisplay": "PicturePack", 62 | "PictureURL": [ 63 | "https://i.ebayimg.com/00/s/NjQwWDY0MA==/z/u7cAAOSwTEZcS8yS/$_32.JPG?set_id=880000500F", 64 | "https://i.ebayimg.com/00/s/ODE3WDgxNw==/z/VtcAAOSw2zRcS8yT/$_32.JPG?set_id=880000500F" 65 | ] 66 | }, 67 | "DispatchTimeMax": "3", 68 | "ItemSpecifics": { 69 | "NameValueList": [ 70 | { 71 | "Name": "Band Material", 72 | "Value": "Synthetic Leather" 73 | }, 74 | { 75 | "Name": "Brand", 76 | "Value": "SOKI" 77 | }, 78 | { 79 | "Name": "Year of Manufacture", 80 | "Value": "2010-Now" 81 | }, 82 | { 83 | "Name": "Water Resistance Rating", 84 | "Value": "30 m (3 ATM)" 85 | }, 86 | { 87 | "Name": "Model", 88 | "Value": "Fashion" 89 | }, 90 | { 91 | "Name": "Case Size", 92 | "Value": "48mm" 93 | }, 94 | { 95 | "Name": "Face Color", 96 | "Value": [ 97 | "Black", 98 | "Brown", 99 | "White" 100 | ] 101 | }, 102 | { 103 | "Name": "Display", 104 | "Value": "Analog" 105 | }, 106 | { 107 | "Name": "Lug Width", 108 | "Value": "12mm" 109 | }, 110 | { 111 | "Name": "Style", 112 | "Value": "Sport" 113 | }, 114 | { 115 | "Name": "Case Finish", 116 | "Value": "Polished" 117 | }, 118 | { 119 | "Name": "Case Material", 120 | "Value": "Stainless Steel" 121 | }, 122 | { 123 | "Name": "Movement", 124 | "Value": "Quartz (Battery)" 125 | }, 126 | { 127 | "Name": "Features", 128 | "Value": [ 129 | "12-hour Dial", 130 | "Date Indicator", 131 | "Day Indicator" 132 | ] 133 | }, 134 | { 135 | "Name": "Water Resistance", 136 | "Value": "30 Metres / 3 ATM" 137 | }, 138 | { 139 | "Name": "Case Color", 140 | "Value": "Black" 141 | }, 142 | { 143 | "Name": "Age Group", 144 | "Value": "Adult" 145 | }, 146 | { 147 | "Name": "Band Color", 148 | "Value": [ 149 | "Black", 150 | "Brown" 151 | ] 152 | }, 153 | { 154 | "Name": "Gender", 155 | "Value": "Men's" 156 | }, 157 | { 158 | "Name": "Watch Shape", 159 | "Value": "Round" 160 | }, 161 | { 162 | "Name": "MPN", 163 | "Value": "Does Not Apply" 164 | } 165 | ] 166 | }, 167 | "ReturnPolicy": { 168 | "ReturnsAcceptedOption": "ReturnsNotAccepted", 169 | "ReturnsAccepted": "No returns accepted", 170 | "InternationalReturnsAcceptedOption": "ReturnsNotAccepted" 171 | }, 172 | "ConditionID": "1000", 173 | "eBayPlus": "false" 174 | }} -------------------------------------------------------------------------------- /newCookies.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-gray-dev/DropShipper/48142547c471820b97ddae2832898c69e10e71b8/newCookies.pickle -------------------------------------------------------------------------------- /proxyList.txt: -------------------------------------------------------------------------------- 1 | 47.254.94.44:443 2 | 103.106.119.154:8080 3 | 94.100.11.250:45746 4 | 203.177.133.148:44888 5 | 36.85.143.249:8080 6 | 69.74.176.16:8080 7 | 172.104.18.77:3128 8 | 73.55.76.54:8080 9 | 98.172.142.6:8080 10 | 35.245.7.178:3128 11 | 152.44.45.138:3128 12 | 24.172.34.114:57600 13 | 138.197.108.5:3128 14 | 107.191.45.149:8118 15 | 74.213.63.78:45858 16 | 12.201.144.252:8080 17 | 172.80.54.67:8080 18 | 68.177.70.229:54676 19 | 66.82.22.79:80 20 | 35.203.190.24:3128 21 | 12.205.32.193:53281 22 | 165.22.7.138:8080 23 | 205.202.42.230:8083 24 | 45.63.66.17:8080 25 | 208.76.103.47:8080 26 | 104.236.48.178:8080 27 | 18.237.16.174:3128 28 | 68.183.106.108:8080 29 | 96.74.27.161:32784 30 | 173.208.187.10:3128 31 | 65.152.119.226:40418 32 | 152.44.45.203:3128 33 | 108.61.222.139:8118 34 | 35.246.39.130:3128 35 | 40.114.109.214:3128 36 | 204.15.243.233:57446 37 | -------------------------------------------------------------------------------- /workingTemp.json: -------------------------------------------------------------------------------- 1 | {"Item": { 2 | "Title": "Harry Potter and the Philosopher's Stone", 3 | "Description": "This is the first book in the Harry Potter series. In excellent condition!", 4 | "PrimaryCategory": {"CategoryID": "377"}, 5 | "StartPrice": "10.0", 6 | "BuyItNowPrice": "15.0", 7 | "Location": "Shanghai", 8 | "CategoryMappingAllowed": "true", 9 | "Country": "CN", 10 | "ConditionID": "3000", 11 | "Currency": "USD", 12 | "DispatchTimeMax": "3", 13 | "ListingDuration": "Days_7", 14 | "ListingDetails": { 15 | "MinimumBestOfferPrice": "6.0" 16 | }, 17 | "ItemSpecifics": { 18 | "NameValueList": [ 19 | { 20 | "Name": "Band Material", 21 | "Value": "Synthetic Leather" 22 | }, 23 | { 24 | "Name": "Brand", 25 | "Value": "SOKI" 26 | }, 27 | { 28 | "Name": "Year of Manufacture", 29 | "Value": "2010-Now" 30 | }, 31 | { 32 | "Name": "Water Resistance Rating", 33 | "Value": "30 m (3 ATM)" 34 | }, 35 | { 36 | "Name": "Model", 37 | "Value": "Fashion" 38 | }, 39 | { 40 | "Name": "Case Size", 41 | "Value": "48mm" 42 | }, 43 | { 44 | "Name": "Face Color", 45 | "Value": [ 46 | "Black", 47 | "Brown", 48 | "White" 49 | ] 50 | }, 51 | { 52 | "Name": "Display", 53 | "Value": "Analog" 54 | }, 55 | { 56 | "Name": "Lug Width", 57 | "Value": "12mm" 58 | }, 59 | { 60 | "Name": "Style", 61 | "Value": "Sport" 62 | }, 63 | { 64 | "Name": "Case Finish", 65 | "Value": "Polished" 66 | }, 67 | { 68 | "Name": "Case Material", 69 | "Value": "Stainless Steel" 70 | }, 71 | { 72 | "Name": "Movement", 73 | "Value": "Quartz (Battery)" 74 | }, 75 | { 76 | "Name": "Features", 77 | "Value": [ 78 | "12-hour Dial", 79 | "Date Indicator", 80 | "Day Indicator" 81 | ] 82 | }, 83 | { 84 | "Name": "Water Resistance", 85 | "Value": "30 Metres / 3 ATM" 86 | }, 87 | { 88 | "Name": "Case Color", 89 | "Value": "Black" 90 | }, 91 | { 92 | "Name": "Age Group", 93 | "Value": "Adult" 94 | }, 95 | { 96 | "Name": "Band Color", 97 | "Value": [ 98 | "Black", 99 | "Brown" 100 | ] 101 | }, 102 | { 103 | "Name": "Gender", 104 | "Value": "Men's" 105 | }, 106 | { 107 | "Name": "Watch Shape", 108 | "Value": "Round" 109 | }, 110 | { 111 | "Name": "MPN", 112 | "Value": "Does Not Apply" 113 | } 114 | ] 115 | }, 116 | "ListingType": "Chinese", 117 | "PaymentMethods": "PayPal", 118 | "PayPalEmailAddress": "tkeefdddder@gmail.com", 119 | "PictureDetails": { 120 | "PhotoDisplay": "PicturePack", 121 | "PictureURL": [ 122 | "https://i.ebayimg.com/00/s/NjQwWDY0MA==/z/u7cAAOSwTEZcS8yS/$_32.JPG?set_id=880000500F", 123 | "https://i.ebayimg.com/00/s/ODE3WDgxNw==/z/VtcAAOSw2zRcS8yT/$_32.JPG?set_id=880000500F" 124 | ] 125 | }, 126 | "Quantity": "1", 127 | "ReturnPolicy": { 128 | "ReturnsAcceptedOption": "ReturnsNotAccepted", 129 | "ReturnsAccepted": "No returns accepted", 130 | "InternationalReturnsAcceptedOption": "ReturnsNotAccepted" 131 | }, 132 | "SellerProfiles": { 133 | "SellerPaymentProfile": { 134 | "PaymentProfileName": "PayPal:Immediate pay" 135 | }, 136 | "SellerReturnProfile": { 137 | "ReturnProfileName": "30 Day Return Policy" 138 | }, 139 | "SellerShippingProfile": { 140 | "ShippingProfileName": "China Post Ordinary Small Packet Plus" 141 | } 142 | }, 143 | "ShippingDetails": { 144 | 145 | "SalesTax": { 146 | "SalesTaxPercent": "0.0", 147 | "ShippingIncludedInTax": "false" 148 | }, 149 | "ShippingServiceOptions": { 150 | "ShippingService": "US_StandardShippingFromGC", 151 | "ShippingServiceCost": "0.0", 152 | "ShippingServicePriority": "1", 153 | "ExpeditedService": "false", 154 | "ShippingTimeMin": "7", 155 | "ShippingTimeMax": "19", 156 | "FreeShipping": "true" 157 | }, 158 | "ShippingType": "Flat", 159 | "ShippingDiscountProfileID": "0", 160 | "InternationalShippingDiscountProfileID": "0" 161 | }, 162 | "ShipToLocations": "US", 163 | "Site": "US" 164 | } 165 | } --------------------------------------------------------------------------------