kacpi2442 / am_bot

Using the toogoodtogo and foodsi APIs to notify me via a Telegram bot for open offers
39 stars 19 forks source link

Foodsi - uwierzytelnianie #29

Open Wiedmolol opened 1 year ago

Wiedmolol commented 1 year ago

Wygląda na to, że Foodsi wprowadziło dzisiaj autentykację do requestów po API. W tym momencie API zwraca:

{
    "errors": [
        "Aby kontynuować zaloguj lub zarejestruj się."
    ]
}
cmjr86 commented 1 year ago

Hi, Just noted that also: [] {'page': 1, 'per_page': 15, 'distance': {'lat': 50.038838, 'lng': 19.9600552, 'range': 15000}, 'hide_unavailable': True, 'food_type': ['meals', 'bakery', 'shop'], 'collection_time': {'from': '00:00:00', 'to': '23:59:59'}} {'errors': ['Aby kontynuować zaloguj lub zarejestruj się.']}

@kacpi2442

antonioli86 commented 1 year ago

I think I found the endpoint to login but I don't know what to from here. Tried to get cookie but its empty. POST https://api.foodsi.pl/api/v2/auth/sign_in with body { "email":"...", "password":"..." }

Then I have 200 and returns the user details

{'data': {'id': xxxxx, 'provider': 'email', 'uid': 'xxxxx, 'email': 'xxxxx', 'name': 'xxxx', 'phone': None, 'picture': {'url': None}, 'phone_validation': None, 'phone_validated_at': None, 'allow_password_change': False, 'favourite_restaurants': []}}

grutkowski-doz commented 1 year ago

dobra robota @antonioli86 - pokombinowałem i te headery, które dostajesz w zwrotce od autoryzacji przekaż do requestu, a konkretniej to wystraczy: Access-Token, Uid, Client --- You need to pass Access-Token, Uid, Client headers from auth response

antonioli86 commented 1 year ago

dobra robota @antonioli86 - pokombinowałem i te headery, które dostajesz w zwrotce od autoryzacji przekaż do requestu, a konkretniej to wystraczy: Access-Token, Uid, Client --- You need to pass Access-Token, Uid, Client headers from auth response

Dzięki @grutkowski-doz ! It works... it did a "drewniany" script and hardcoded the header parameters from the answer of auth in the header of the POST to restaurants and it works like a charm!

foodsi_api = requests.post('https://api.foodsi.pl/api/v2/restaurants', headers = {'Content-type':'application/json', 'system-version':'android_3.0.0', 'user-agent':'okhttp/3.12.0', 'Access-Token': 'xxxxxxxxxx', 'Client': 'xxxxxxxxxxx', 'Uid': 'xxxxxxxx'}, data = json.dumps(req_json))

And answer with the items:


{'page': 1, 'per_page': 15, 'distance': {'lat': '50.038838', 'lng': '19.9600552', 'range': '15000'}, 'hide_unavailable': True, 'food_type': ['meals', 'bakery', 'shop'], 'collection_time': {'from': '00:00:00', 'to': '23:59:59'}}
{'total_pages': 1, 'current_page': 1, 'data': [{'id': 1268582, 'address': 'Henryka Kamieńskiego 11, Kraków', 'address_notes': '', 'distance': '1.2', 'image': {'url': 'https://foodsi-backend-production.s3.amazonaws.com/uploads/package/image/2805/d53173efeb.jpg'}, 'important_notes': '', 'latitude': '50.02861492917916', 'logo': {'url': 'https://foodsi-backend-production.s3.amazonaws.com/uploads/venue/logo/1906/6c94f65f63.jpg'}, 'longitude': '19.95326270135493', 'meal': {'description': '🥗🥘🍝 Restauracja Olimp to miejsce, w którym uratujesz pyszne dania obiadowe, surówki i zupy.  ', 'original_price': '54.0', 'price': '17.99'}, 'name': 'OLIMP BONARKA', 'package_day': {'availability_label': 'OSTATNIA PACZKA', 'availability_label_number': 1, 'isNew': False, 'collection_day': {'id': 1268582, 'week_day': 7, 'schedule_id': 1, 'active': True, 'closed_at': '2000-01-01T17:30:00.000Z', 'opened_at': '2000-01-01T16:00:00.000Z'}, 'meals_left': 1, 'soldOut': None}, 'package_id': 2805, 'package_type': 'paczka niespodzianka'
....

Now is just necessary to automate it and do in proper way! :P Thanks again grutkowski-doz

grutkowski-doz commented 1 year ago

@boofuls w zwrotce samej nie, ale w zwróconych headerach, jeśli używasz postmana to wchodzisz w zakładkę Headers zamiast Body obraz

cmjr86 commented 1 year ago

Skąd wziąć Access-Token? W zwrotce od autoryzacji nie jest podany...

In the headers.

boofuls commented 1 year ago

Dzięki @grutkowski-doz :)

Kirri777 commented 1 year ago

Zrobiłem u siebie poprawkę, możecie to wprowadzić w swoich aplikacjach po odpowiednim dostosowaniu ;)


    def _setHeaders(self) -> None:
        try:
            self._headers = {
                'Content-type':'application/json',
                'system-version':'android_3.0.0',
                'user-agent':'okhttp/3.12.0',
                'Access-Token': self._config.get('foodsi_access_token'),
                'Client': self._config.get('foodsi_client'),
                'Uid': self._config.get('foodsi_uid')
            }
        except Exception as e:
            self._log.add('Foodsi [_setHeaders]', str(e), str(traceback.format_exc()))
            exit(1)

    def newCredentials(self) -> None:
        try:
            req_json = {
                'email' : self._config.get('foodsi_email'),
                'password' : self._getPassword(),
            }

            response = requests.post('https://api.foodsi.pl/api/v2/auth/sign_in', headers = {'Content-type':'application/json', 'system-version':'android_3.0.0', 'user-agent':'okhttp/3.12.0'}, data = json.dumps(req_json), timeout = 10)

            if response.status_code == 200:
                self._config.update('foodsi_access_token', str(response.headers['Access-Token']))
                self._config.update('foodsi_client', str(response.headers['Client']))
                self._config.update('foodsi_uid', str(response.headers['Uid']))
                return self._setHeaders()

            self._log.add('Foodsi [newCredentials]', str(response.json()))
            time.sleep(150)
            return self.newCredentials()
        except Exception as e:
            self._log.add('Foodsi [newCredentials]', str(e), str(traceback.format_exc()))
            self._telegram.sendText(str(traceback.format_exc()))
            exit(1)
Robertnoob12 commented 1 year ago

pomoże ktoś jak zrobić zeby ten program działał?

Robertnoob12 commented 1 year ago

nie mogę nic znaleźć :(

Robertnoob12 commented 1 year ago

@Kirri777 @antonioli86

antonioli86 commented 1 year ago

@Kirri777 @antonioli86

* please help

* proszę o pomoc

watch_script.zip

I attached my version of watch_script.py You need also to extend the config.json

{
    "telegram": {
        "bot_token": "xyz",
        "bot_chatID": -00000
    },
    "location": {
        "lat": 0.0,
        "long": 0.0,
        "range": 15
    },
    "auth": {
        "email": "xxxx",
        "password": "xxxxx"
    }
}

Basic code. ;) Working so far... time to time foodsi API is down during night and my container goes do - I need to start manually.

Robertnoob12 commented 1 year ago

okay, will try now and let you know. thanks for the response and for the help

antonioli86 commented 1 year ago

okay, will try now and let you know. thanks for the response and for the help

No problem. Is not a "sexy" code but works for me. :)

Robertnoob12 commented 1 year ago

File "C:\Users\rober\OneDrive\Pulpit\am_bot-main\watch_script.py", line 366, in refresh foodsi() File "C:\Users\rober\OneDrive\Pulpit\am_bot-main\watch_script.py", line 282, in foodsi items += parse_foodsi_api(foodsi_api.json()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\rober\OneDrive\Pulpit\am_bot-main\watch_script.py", line 249, in parse_foodsi_api for restaurant in api_result['data']:


KeyError: 'data'
Robertnoob12 commented 1 year ago

sends me this, i think its about the api right?

Robertnoob12 commented 1 year ago

{ "telegram": { "bot_token": "xxx", "bot_chatID": xxx }, "location": { "lat": xxx, "long": xx, "range": 10 }, "auth": { "email": "xxx", "password": "xxx" }, "tgtg": { "access_token": "xxx", "refresh_token": "xxx", "user_id": "xxx", "cookie": "xx" } }

Robertnoob12 commented 1 year ago

thats how i setup the config by the way , the TGTG part got added after

Robertnoob12 commented 1 year ago

@antonioli86 sorry for mentioning (if its annoying let me know)

Robertnoob12 commented 1 year ago

i think its due to the api being down?

Robertnoob12 commented 1 year ago

nvm, saw the file lol.

antonioli86 commented 1 year ago

I only use the code for the Foodsi btw. I removed the TGTG part. ;) For TGTG I use https://github.com/ahivert/tgtg-python

edit: correction: I use https://github.com/Der-Henning/tgtg

Robertnoob12 commented 1 year ago

thanks mate, u really helped me with that

Robertnoob12 commented 1 year ago

hey man, how can i start the TGTG u mentioned lol? i cant find the .py file or .exe to use it i installed with pip but cant find it @antonioli86

Robertnoob12 commented 1 year ago

or i have to make it?

antonioli86 commented 1 year ago

I have a container (I built with Docker). But you can run manually: python3 watch_script.py

.py file is zipped in the message above: imagem

Robertnoob12 commented 1 year ago

foodsi works perfectly fine, but i dont know about too good to go

Robertnoob12 commented 1 year ago

i think i have to make too good to go .py file manually? from the info on there: https://github.com/ahivert/tgtg-python

antonioli86 commented 1 year ago

foodsi works perfectly fine, but i dont know about too good to go

Sorry for that. :( But in general I just removed the TGTG part from original file - https://github.com/kacpi2442/am_bot/blob/main/watch_script.py - and extended the Foodsi with authentication.

Robertnoob12 commented 1 year ago

@antonioli86 do you speak polish?

Robertnoob12 commented 1 year ago

if not:

Robertnoob12 commented 1 year ago

@antonioli86 could you help

antonioli86 commented 1 year ago

@antonioli86 could you help

My mistake - to TGTG I use https://github.com/Der-Henning/tgtg. If you don't want to run as container you have option "Run from source".

I did not merged - I use am_bot to Foodsi, /Der-Henning/tgtg to TGTG.

Robertnoob12 commented 1 year ago

it works 100%? because i never had a notification when shop got from sold to items available @antonioli86

antonioli86 commented 1 year ago

it works 100%? because i never had a notification when shop got from sold to items available @antonioli86

https://github.com/Der-Henning/tgtg works perfectly to me. Need keep in mind it uses crontab... maximum can do is to check every minute.

My Docker compose:

version: "3.3"
services:
  app:
    image: derhenning/tgtg:latest         ## pre build image from docker hub
    environment:
      - TZ=Europe/Warsaw                              ## Set timezone for pickupdate, otherwise will be utc
      - DEBUG=false                                   ## true for debug log messages
      - TGTG_USERNAME= xxxx@xxx          ## TGTG Username / Login EMail
      - SLEEP_TIME=60                                 ## Time to wait till next scan in seconds - default 60 seconds
      - SCHEDULE_CRON=* 7-23 * * *                ## (optional) Scheduler in cron schedule expression
                                                      ## Example of cron schedule expression:
                                                      ## - SCHEDULE_CRON=* 12-14 * * 1-5
                                                      ## => allowed to run at hours between 12:00 and 14:59 on monday to friday
                                                      ## more help with formatting at https://crontab.guru/#*_12-14_*_*_1-5
                                                      ## The Scanner will not make any requests to the TGTG API in the excluded periods
      - METRICS=false                                 ## Enable to export metrics for Prometheus
      - METRICS_PORT=8000                             ## Port for metrics http server
      - DISABLE_TESTS=false                           ## true to disable test notifications on startup
      - QUIET=false                                   ## true to disable all console messages. Only errors and console notifier messages
      - LOCALE=en_US                                  ## set locale to localize ${{pickupdate}}

      - CONSOLE=false                                 ## true = enable simple console notifications
      #- CONSOLE_BODY=                                ## console message with variables as described below
      #- CONSOLE_CRON=                                ## Disable notifications based on cron

      - TELEGRAM=true                                ## true = enable notifications via Telegram
      - TELEGRAM_TOKEN=62..................## Telegram Bot token - see @botfather
      - TELEGRAM_CHAT_IDS=-67..........                            ## Telegram Chat id, multiple ids separated by comma
      - TELEGRAM_TIMEOUT=60                           ## Timeout for Telegram API requests
      #- TELEGRAM_CRON=
      #- TELEGRAM_BODY=                               ## Optional message body as markdown, variables as described below
                                                      ## Example:
                                                      ## 'TELEGRAM_BODY=*$${{display_name}}*\n*Available*: $${{items_available}}\n*Rating*: $${{rating}}\n*Price*: $${{price}} $${{currency}}\n*Pickup*: $${{pickupdate}}'
                                                      ## In some cases you may have to add
                                                      ## 'TELEGRAM_BODY={{` ... `}}'

    volumes:
      - tokens:/tokens           ## volume to save TGTG credentials to reuse on next start up and avoid login mail
volumes:
  tokens:
Robertnoob12 commented 1 year ago

@antonioli86 what is the sleep time and schedule cron for

antonioli86 commented 1 year ago

@antonioli86 what is the sleep time and schedule cron for

Cron is basically will define the intervals when will run the scanner. It is the the documentation: imagem https://github.com/Der-Henning/tgtg/wiki/Configuration

My crontab is defined to run from 7:00 till 23:59 to scan every minute.

antonioli86 commented 1 year ago

foodsi works perfectly fine, but i dont know about too good to go

Which errors you have with TGTG using am_bot?

Robertnoob12 commented 1 year ago

TGTG on am works good but i dont know how to make it work with foodsi

antonioli86 commented 1 year ago

TGTG on am works good but i dont know how to make it work with foodsi

You mean the original file - https://github.com/kacpi2442/am_bot/blob/main/watch_script.py - works fine with TGTG, right? You just need to edit the foodsi function - def foodsi():

def foodsi():
    """
    Retrieves the data from foodsi API and selects the message to send.
    """
    try:
            req_json = {
                'email' : config['auth']['email'],
                'password' : config['auth']['password']
            }

            response = requests.post('https://api.foodsi.pl/api/v2/auth/sign_in', headers = {'Content-type':'application/json', 'system-version':'android_3.0.0', 'user-agent':'okhttp/3.12.0'}, data = json.dumps(req_json), timeout = 10)

            if response.status_code == 200:
                access_token = str(response.headers['Access-Token'])
                client = str(response.headers['Client'])
                uid = str(response.headers['Uid'])
    except Exception as e:
        telegram_bot_sendtext(str(e))
        exit(1)

    items = list()
    page = 1
    totalpages = 1
    while page <= totalpages:
        req_json = {
            "page": page,
            "distance": {
            "per_page": 15,
                "lat": config['location']['lat'],
                "lng": config['location']['long'],
                "range": config['location']['range']*1000
            },
            "hide_unavailable": True,
            "food_type": ['meals','bakery','shop'],
            "collection_time": {
                "from": "00:00:00",
                "to": "23:59:59"
            }
        }
        foodsi_api = requests.post('https://api.foodsi.pl/api/v2/restaurants', headers = {'Content-type':'application/json', 'system-version':'android_3.0.0', 'user-agent':'okhttp/3.12.0', 'Access-Token': access_token, 'Client': client, 'Uid': uid}, data = json.dumps(req_json))
        items += parse_foodsi_api(foodsi_api.json())
        totalpages = foodsi_api.json()['total_pages']
        page += 1
    print("Foodsi total items: " + str(len(items)))
    # Get the global variable of items in stock
    global foodsi_in_stock

    # Go through all favourite items and compare the stock
    for item in items:
        try:
            old_stock = [stock['package_day']['meals_left'] for stock in foodsi_in_stock if stock['id'] == item['id']][0]
        except IndexError:
            old_stock = 0
        try:
            item['msg_id'] = [stock['msg_id'] for stock in foodsi_in_stock if stock['id'] == item['id']][0]
        except:
            pass

        new_stock = item['package_day']['meals_left']

        # Check, if the stock has changed. Send a message if so.
        if new_stock != old_stock:
            # Check if the stock was replenished, send an encouraging image message
            if old_stock == 0 and new_stock > 0:
                #TODO: tommorrow date
                message = f"🍽 There are {new_stock} new goodie bags at [{item['name']}]({item['url']})\n"\
                f"_{item['meal']['description']}_\n"\
                f"_{item['package_type']}_\n"\
                f"💰 *{item['meal']['price']}PLN*/{item['meal']['original_price']}PLN\n"\
                f"⏰ {item['opened_at']}-{item['closed_at']}\n"\
                "ℹ️ foodsi.pl"
                # message += f"\ndebug id: {item['id']}"
                tg = telegram_bot_sendimage(item['image']['url'], message)
                try: 
                    item['msg_id'] = tg['result']['message_id']
                except:
                    print(json.dumps(tg))
             ####   print(item['image']['url'])
                    print(message)
                    print(traceback.format_exc())
            elif old_stock > new_stock and new_stock != 0:
                # customer feedback: This message is not needed
                pass
                ## Prepare a generic string, but with the important info
                # message = f" 📉 Decrease from {old_stock} to {new_stock} available goodie bags at {[item['name'] for item in new_api_result if item['id'] == item_id][0]}."
                # telegram_bot_sendtext(message)
            elif old_stock > new_stock and new_stock == 0:
                # message = f" ⭕ Sold out! There are no more goodie bags available at {item['name']}."
                # telegram_bot_sendtext(message)
                try: 
                    tg = telegram_bot_delete_message([stock['msg_id'] for stock in foodsi_in_stock if stock['id'] == item['id']][0])
                except:
                    print(f"Failed to remove message for item id: {item['id']}")
                    print(traceback.format_exc())
            else:
                # Prepare a generic string, but with the important info
                message = f"There was a change of number of goodie bags in stock from {old_stock} to {new_stock} at {item['name']}."
                telegram_bot_sendtext(message)

    # Reset the global information with the newest fetch
    foodsi_in_stock = items

    # Print out some maintenance info in the terminal
    print(f"Foodsi: API run at {time.ctime(time.time())} successful.")
    # for item in foodsi_in_stock:
    #     print(f"{item['name']}({item['id']}): {item['package_day']['meals_left']}")
Robertnoob12 commented 1 year ago

yeah

antonioli86 commented 1 year ago

yeah

Just edit the foodsi function with my code. It should work.

antonioli86 commented 1 year ago

From original code just added the authentication part in the beginning of foodsi function:

   try:
            req_json = {
                'email' : config['auth']['email'],
                'password' : config['auth']['password']
            }

            response = requests.post('https://api.foodsi.pl/api/v2/auth/sign_in', headers = {'Content-type':'application/json', 'system-version':'android_3.0.0', 'user-agent':'okhttp/3.12.0'}, data = json.dumps(req_json), timeout = 10)

            if response.status_code == 200:
                access_token = str(response.headers['Access-Token'])
                client = str(response.headers['Client'])
                uid = str(response.headers['Uid'])
    except Exception as e:
        telegram_bot_sendtext(str(e))
        exit(1)

Then I updated the request to include the Access Token, client and uid (from the response of the authentication before): foodsi_api = requests.post('https://api.foodsi.pl/api/v2/restaurants', headers = {'Content-type':'application/json', 'system-version':'android_3.0.0', 'user-agent':'okhttp/3.12.0', 'Access-Token': access_token, 'Client': client, 'Uid': uid}, data = json.dumps(req_json))

Robertnoob12 commented 1 year ago

it crashes instantly

antonioli86 commented 1 year ago

it crashes instantly

Errors? And config.json includes

 "auth": {
        "email": "xxxx@wp.pl",
        "password": "xxxxx"
    }

?

Robertnoob12 commented 1 year ago
"auth": {
    "email": "xxxxxx@gmail.com",
    "password": "xxxx"
Robertnoob12 commented 1 year ago

yeah

Robertnoob12 commented 1 year ago

idk which error, it just flashes and closes the python file

antonioli86 commented 1 year ago

idk which error, it just flashes and closes the python file

And no telegram message?

Robertnoob12 commented 1 year ago

yeah