├── requirements.txt ├── LICENSE ├── README.md ├── .gitignore └── tenbis-report.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dvir Perets 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Find unused 10bis barcodes (Shufersal/Victory) 2 | ## ** PLEASE NOTE ** - 10Bis updates their barcodes usage state every week or two, so consider only records older than two weeks as valid) 3 | 4 | * You should also checkout this repo https://github.com/Dvirus89/tenbis-buy-coupons to automatically purchase Shufersal coupons using the best optimized combination to utilize all the remaining credit. 5 | 6 | ## Usage - Windows: 7 | ``` 8 | git clone https://github.com/Dvirus89/tenbisbarcodes 9 | cd tenbisbarcodes 10 | python3 -m venv tenbisbarcodes 11 | tenbisbarcodes/bin/activate 12 | pip3 install -r requirements.txt 13 | python3 tenbis-report.py 14 | deactivate 15 | ``` 16 | ## Usage - Linux/MacOS: 17 | ``` 18 | git clone https://github.com/Dvirus89/tenbisbarcodes 19 | cd tenbisbarcodes 20 | python3 -m venv tenbisbarcodes 21 | source tenbisbarcodes/bin/activate 22 | pip3 install -r requirements.txt 23 | python3 tenbis-report.py 24 | deactivate 25 | ``` 26 | 27 | 28 | ## Release for Windows user: 29 | Download https://github.com/Dvirus89/tenbisbarcodes/releases/download/v1.0-beta/tenbis-report.exe 30 | 31 | (MD5 f71826471df37529420081033e942df5) 32 | 33 | 34 | ## The output: 35 | HTML report - the easiest way to use the barcodes. 36 | 37 | 1. Just save the HTML file to your mobile device 38 | 2. Click "Hide All" - to hide all the barcodes 39 | 3. Click in the gray area to expose a single barcode 40 | 4. Scan the barcode with the BLACK barcode scanners. (images below) 41 | 5. Click the used barcode to hide it 42 | 6. Repeat (from step 3) 43 | 44 | ![image](https://user-images.githubusercontent.com/1368112/212760973-912c3332-6915-4cb4-8ab1-c00006011891.png) 45 | 46 | 47 | ## Identify the barcode scanners: 48 | ![image](https://user-images.githubusercontent.com/1368112/212545083-bdefee29-b4b9-4e9a-b28f-20d7860948d8.png) 49 | 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .DS_Store 131 | 132 | # private user files 133 | *.html 134 | sessions.pickle 135 | usertoken.pickle -------------------------------------------------------------------------------- /tenbis-report.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | import pickle 4 | import urllib3 5 | import json 6 | from datetime import date 7 | 8 | 9 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 10 | CWD=os.getcwd() 11 | SESSION_PATH = f"{CWD}/sessions.pickle" 12 | TOKEN_PATH = f"{CWD}/usertoken.pickle" 13 | FILENAME = f"report-{date.today().strftime('%d-%b-%Y')}.html" 14 | OUTPUT_PATH = f"{CWD}/{FILENAME}" 15 | TENBIS_FQDN = "https://www.10bis.co.il" 16 | DEBUG = False 17 | HTML_ROW_TEMPLATE = """ 18 | {counter} {store} {order_date} {barcode_number} {amount} {valid_date}

19 | """ 20 | HTML_PAGE_TEMPLATE = """ 21 | 22 | 23 | 24 | 44 | 87 | 88 | 89 |

Non used barcodes

90 | 91 | 92 | {output_table} 93 |
Item number Store Order date Barcode number Barcode image
Amount Expiration date
94 | 95 | 96 | """ 97 | 98 | def main_procedure(): 99 | # If token exists, use the token to authenticate 10bis 100 | if os.path.exists(SESSION_PATH) and os.path.exists(TOKEN_PATH): 101 | session = load_pickle(SESSION_PATH) 102 | user_token = load_pickle(TOKEN_PATH) 103 | session.user_token = user_token 104 | 105 | # If there's no token, authenticate 10bis and extract auth tokens 106 | else: 107 | session = auth_tenbis() 108 | if(not session): 109 | print("exit") 110 | return 111 | create_pickle(session,SESSION_PATH) 112 | 113 | rows_data='' 114 | count = 0 115 | total_amount = 0 116 | years_to_check = -abs(input_number('How many years back to scan? ')) * 12 117 | for num in range(0, years_to_check, -1): 118 | month_json_result = get_report_for_month(session, str(num)) 119 | for order in month_json_result: 120 | used, barcode_number, barcode_img_url, amount, valid_date = get_barcode_order_info(session, order['orderId'], order['restaurantId']) 121 | if not used: 122 | rows_data += HTML_ROW_TEMPLATE.format(counter=str(count), store=order['restaurantName'], order_date=order['orderDateStr'], barcode_number=barcode_number, 123 | barcode_img_url=barcode_img_url, amount=amount, valid_date=valid_date) 124 | count+=1 125 | total_amount += int(amount) 126 | print("Token found! ", count, order['orderDateStr'], barcode_number, barcode_img_url, amount, valid_date) 127 | 128 | if count > 0: 129 | write_file(OUTPUT_PATH, HTML_PAGE_TEMPLATE.format(output_table=rows_data)) 130 | print(f"{str(count)} tokens were found with total of {total_amount} NIS!") 131 | print(f'Please find your report here: {CWD} ({FILENAME})') 132 | else: 133 | print('No tokens were found.') 134 | 135 | def input_number(message): 136 | while True: 137 | try: 138 | userInput = int(input(message)) 139 | except ValueError: 140 | print("Not an integer! Try again. (examples: 1,2,3,4,5)") 141 | continue 142 | else: 143 | return userInput 144 | break 145 | 146 | def write_file(path, content): 147 | with open(path, 'w', encoding='utf-8') as f: 148 | f.write(content) 149 | 150 | def create_pickle(obj, path): 151 | with open(path, 'wb') as session_file: 152 | pickle.dump(obj, session_file) 153 | 154 | def load_pickle(path): 155 | with open(path, 'rb') as session_file: 156 | objfrompickle = pickle.load(session_file) 157 | return objfrompickle 158 | 159 | def print_hebrew(heb_txt): 160 | print(heb_txt[::-1]) 161 | 162 | def get_report_for_month(session, month): 163 | endpoint = TENBIS_FQDN + "/NextApi/UserTransactionsReport" 164 | payload = {"culture": "he-IL", "uiCulture": "he", "dateBias": month} 165 | headers = { 166 | "content-type": "application/json", 167 | "user-token": session.user_token, 168 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", 169 | "Accept": "application/json, text/plain, */*", 170 | "Accept-Language": "he-IL,he;q=0.9,en;q=0.8", 171 | "Referer": "https://www.10bis.co.il/" 172 | } 173 | response = session.post(endpoint, data=json.dumps(payload), headers=headers, verify=False) 174 | if(DEBUG): 175 | print(endpoint + "\r\n" + str(response.status_code) + "\r\n" + response.text) 176 | resp_json = json.loads(response.text) 177 | error_msg = resp_json['Errors'] 178 | success_code = resp_json['Success'] 179 | if(not success_code): 180 | print_hebrew((error_msg[0]['ErrorDesc'])) 181 | return 182 | 183 | all_orders = resp_json['Data']['orderList'] 184 | barcode_orders = [x for x in all_orders if x['isBarCodeOrder'] == True] 185 | 186 | return barcode_orders 187 | 188 | def get_barcode_order_info(session, order_id, res_id): 189 | endpoint = TENBIS_FQDN + f"/NextApi/GetOrderBarcode?culture=he-IL&uiCulture=he&orderId={order_id}&resId={res_id}" 190 | headers = { 191 | "content-type": "application/json", 192 | "user-token": session.user_token, 193 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", 194 | "Accept": "application/json, text/plain, */*", 195 | "Accept-Language": "he-IL,he;q=0.9,en;q=0.8", 196 | "Referer": "https://www.10bis.co.il/" 197 | } 198 | response = session.get(endpoint, headers=headers, verify=False) 199 | if(DEBUG): 200 | print(endpoint + "\r\n" + str(response.status_code) + "\r\n" + response.text) 201 | resp_json = json.loads(response.text) 202 | error_msg = resp_json['Error'] 203 | success_code = resp_json['Success'] 204 | if(not success_code): 205 | print_hebrew((error_msg['ErrorDesc'])) 206 | print_hebrew("Error, trying moving to the next barcode") 207 | return True, '', '', '', '' 208 | used = resp_json['Data']['Vouchers'][0]['Used'] 209 | 210 | if not used: 211 | barcode_number = resp_json['Data']['Vouchers'][0]['BarCodeNumber'] 212 | barcode_number_formatted = '-'.join(barcode_number[i:i+4] for i in range(0, len(barcode_number), 4)) 213 | barcode_img_url = resp_json['Data']['Vouchers'][0]['BarCodeImgUrl'] 214 | amount = resp_json['Data']['Vouchers'][0]['Amount'] 215 | valid_date = resp_json['Data']['Vouchers'][0]['ValidDate'] 216 | return used, barcode_number_formatted, barcode_img_url, amount, valid_date 217 | 218 | return used, '', '', '', '' 219 | 220 | def auth_tenbis(): 221 | # Phase one -> Email 222 | email = input("Enter email: ") 223 | endpoint = TENBIS_FQDN + "/NextApi/GetUserAuthenticationDataAndSendAuthenticationCodeToUser" 224 | 225 | payload = {"culture": "he-IL", "uiCulture": "he", "email": email} 226 | headers = { 227 | "content-type": "application/json", 228 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", 229 | "Accept": "application/json, text/plain, */*", 230 | "Accept-Language": "he-IL,he;q=0.9,en;q=0.8", 231 | "Referer": "https://www.10bis.co.il/" 232 | } 233 | session = requests.session() 234 | 235 | response = session.post(endpoint, data=json.dumps(payload), headers=headers, verify=False) 236 | if(DEBUG): 237 | print(endpoint + "\r\n" + str(response.status_code) + "\r\n" + response.text) 238 | resp_json = json.loads(response.text) 239 | error_msg = resp_json['Errors'] 240 | 241 | if (200 <= response.status_code <= 210 and (len(error_msg) == 0)): 242 | print("User exist, next step is...") 243 | else: 244 | print("login failed") 245 | print_hebrew((error_msg[0]['ErrorDesc'])) 246 | return False 247 | 248 | # Phase two -> OTP 249 | endpoint = TENBIS_FQDN + "/NextApi/GetUserV2" 250 | auth_token = resp_json['Data']['codeAuthenticationData']['authenticationToken'] 251 | shop_cart_guid = resp_json['ShoppingCartGuid'] 252 | 253 | otp = input("Enter OTP: ") 254 | payload = {"shoppingCartGuid": shop_cart_guid, 255 | "culture":"he-IL", 256 | "uiCulture":"he", 257 | "email": email, 258 | "authenticationToken": auth_token, 259 | "authenticationCode": otp} 260 | 261 | response = session.post(endpoint, data=json.dumps(payload), headers=headers, verify=False) 262 | resp_json = json.loads(response.text) 263 | error_msg = resp_json['Errors'] 264 | user_token = resp_json['Data']['userToken'] 265 | if (200 <= response.status_code <= 210 and (len(error_msg) == 0)): 266 | print("login successful...") 267 | else: 268 | print("login failed") 269 | print_hebrew((error_msg[0]['ErrorDesc'])) 270 | return False 271 | 272 | create_pickle(user_token, TOKEN_PATH) 273 | session.user_token = user_token 274 | 275 | if(DEBUG): 276 | print(endpoint + "\r\n" + str(response.status_code) + "\r\n" + response.text) 277 | print(session) 278 | 279 | return session 280 | 281 | if __name__ == '__main__': 282 | main_procedure() 283 | --------------------------------------------------------------------------------