lazeroffmichael / ticktick-py

Unofficial TickTick API

Home Page:https://lazeroffmichael.github.io/ticktick-py/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Getting 503 code when login

vibalcam opened this issue · comments

During the login process:

image

I am getting the following error:

image

When going deeper into the error. It looks like the login process is getting a 503 error. Any ideas on why it might be?

Same issue. I am being redirected to http://127.0.0.1:8080/?error=access_denied&error_description=User%20denied%20access&state=None

@lazeroffmichael also experiencing this issue..

@vibalcam & @eduardocopat did either of you figure out a temporary workaround?

@vibalcam @broskees @eduardocopat

For some reason, I am not able to reproduce the error. Would you guys be able to provide some more information such as the ticktick-py version you are using, python version, and which request it is failing at.

@lazeroffmichael @broskees

My error comes when doing the http_post or http_get methods in api.py. I found a workaround by logging into the website and using Postman to recreate the interaction. I found out that when including the following headers, it all worked fine:

HEADERS = {
        'authority': 'api.ticktick.com',
        'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"',
        'x-device': '{...}',
        'x-requested-with': 'XMLHttpRequest',
        'sec-ch-ua-mobile': '?0',
        'content-type': 'application/json',
        'user-agent': '...',
        'sec-ch-ua-platform': '"Windows"',
        'accept': '*/*',
        'origin': 'https://ticktick.com',
        'sec-fetch-site': 'same-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://ticktick.com/',
        'accept-language': 'es,en-US;q=0.9,en;q=0.8',
    }

Thus, the http_post method would end up being:

def http_post(self, url, **kwargs):
    """
    Sends an http post request with the specified url and keyword arguments.

    Arguments:
        url (str): Url to send the request.
        **kwargs: Arguments to send with the request.

    Returns:
        dict: The json parsed response if possible or just a string of the response text if not.

    Raises:
        RunTimeError: If the request could not be completed.
    """
    if 'headers' not in kwargs:
        response = self._session.post(url, headers=self.HEADERS, **kwargs)
    else:
        kwargs['headers'].update(self.HEADERS)
        response = self._session.post(url, **kwargs)
    self.check_status_code(response, 'Could Not Complete Request')

    try:
        return response.json()
    except ValueError:
        return response.text

Same for http_get and everything works fine.

@vibalcam @broskees @eduardocopat

For some reason, I am not able to reproduce the error. Would you guys be able to provide some more information such as the ticktick-py version you are using, python version, and which request it is failing at.

Hello, @lazeroffmichael! First, thanks for this library :).

This is what I have installed:

Python 3.10.2
Package            Version
------------------ ---------
certifi            2021.10.8
charset-normalizer 2.0.11
idna               3.3
pip                21.2.4
pytz               2021.1
regex              2021.4.4
requests           2.26.0
setuptools         58.1.0
ticktick-py        2.0.2
urllib3            1.26.7
wheel              0.37.1

I am executing the code in a new PC (thus, I don't have the OAuth file). This is my simple script:

import os
import sys
import json
from ticktick.oauth2 import OAuth2        # OAuth2 Manager
from ticktick.api import TickTickClient   # Main Interface

# using get will return `None` if a key is not present rather than raise a `KeyError`
#print())

client = os.environ.get('TICKTICK_CLIENT_ID')
secret = os.environ.get('TICKTICK_CLIENT_SECRET')

import requests

requests_session = requests.session()

auth_client = OAuth2(client_id=client,
                     client_secret=secret,
                     redirect_uri='http://127.0.0.1:8080',
                      session=requests_session)

client = TickTickClient('xxxxx@gmail.com', os.environ.get('TICKTICK_PWD'), auth_client)

#print('fin')
#print()
f = open("tasks.txt", "w")
tasks = client.state['tasks']
f.write(json.dumps(tasks))
f.close()

sys.stdout.flush()

When I run the app, I am redirected to the following page:

image

Then I click allow and I'm redirected to this page:

http://127.0.0.1:8080/?error=access_denied&error_description=User%20denied%20access&state=None

Could it be something with the TickTick API? I'll try vibalcam suggestion above, maybe try to create another "app" at the TickTick API

@vibalcam @eduardocopat @lazeroffmichael

I haven't debugged too deeply, but I'm also getting the error with a simple script:

from toml import load
from ticktick.oauth2 import OAuth2
from ticktick.api import TickTickClient

config = load('config.toml')

auth_client = OAuth2(
    client_id = config["connections"]["ticktick"]["ticktick_client_id"],
    client_secret = config["connections"]["ticktick"]["ticktick_client_secret"],
    redirect_uri = config["connections"]["ticktick"]["ticktick_redirect_uri"]
)

tt = TickTickClient(
    username = config["connections"]["ticktick"]["ticktick_user"],
    password = config["connections"]["ticktick"]["ticktick_pass"],
    oauth = auth_client
)

name = 'Get Groceries'                   # Task Name
local_task = tt.task.builder(name)       # Create a dictionary for the task
groceries = tt.task.create(local_task)   # Actually create the task

and this is the traceback:

Traceback (most recent call last):
  File "/Users/josephroberts/localdev/task-sync-db/test.py", line 21, in <module>
    groceries = tt.task.create(local_task)   # Actually create the task
  File "/usr/local/lib/python3.9/site-packages/ticktick/managers/tasks.py", line 147, in create
    response = self._client.http_post(url=url, json=task, headers=self.oauth_headers)
  File "/usr/local/lib/python3.9/site-packages/ticktick/api.py", line 180, in http_post
    self.check_status_code(response, 'Could Not Complete Request')
  File "/usr/local/lib/python3.9/site-packages/ticktick/api.py", line 117, in check_status_code
    raise RuntimeError(error_message)
RuntimeError: Could Not Complete Request

When I pprint the post requests by editing the api.py http_post() function:

  • I get one with a 200 response and just json of my account details.
  • Then the second response is a 401 response like so (I replaced the access token in the output I pasted here):
{'error': 'invalid_token',
 'error_description': 'Invalid access token: '
                      'MYACCESSTOKEN',
 'errors': [{'message': 'Invalid access token: '
                        'MYACCESSTOKEN'}]}

@lazeroffmichael @broskees

My error comes when doing the http_post or http_get methods in api.py. I found a workaround by logging into the website and using Postman to recreate the interaction. I found out that when including the following headers, it all worked fine:

HEADERS = {
        'authority': 'api.ticktick.com',
        'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"',
        'x-device': '{...}',
        'x-requested-with': 'XMLHttpRequest',
        'sec-ch-ua-mobile': '?0',
        'content-type': 'application/json',
        'user-agent': '...',
        'sec-ch-ua-platform': '"Windows"',
        'accept': '*/*',
        'origin': 'https://ticktick.com',
        'sec-fetch-site': 'same-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://ticktick.com/',
        'accept-language': 'es,en-US;q=0.9,en;q=0.8',
    }

Thus, the http_post method would end up being:

def http_post(self, url, **kwargs):
    """
    Sends an http post request with the specified url and keyword arguments.

    Arguments:
        url (str): Url to send the request.
        **kwargs: Arguments to send with the request.

    Returns:
        dict: The json parsed response if possible or just a string of the response text if not.

    Raises:
        RunTimeError: If the request could not be completed.
    """
    if 'headers' not in kwargs:
        response = self._session.post(url, headers=self.HEADERS, **kwargs)
    else:
        kwargs['headers'].update(self.HEADERS)
        response = self._session.post(url, **kwargs)
    self.check_status_code(response, 'Could Not Complete Request')

    try:
        return response.json()
    except ValueError:
        return response.text

Same for http_get and everything works fine.

@vibalcam what did you place in the x-device field?

I tried @vibalcam's solution, with a small differentiation in the HEADERS constant, then printed out the response codes in http_get() & http_post() - same issue as before.

HEADERS constant:

    HEADERS = {
        'authority': 'api.ticktick.com',
        'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"',
        'x-device-user-agent': USER_AGENT,
        'x-requested-with': 'XMLHttpRequest',
        'sec-ch-ua-mobile': '?0',
        'content-type': 'application/json',
        'User-Agent': USER_AGENT,
        'sec-ch-ua-platform': '"Windows"',
        'accept': '*/*',
        'origin': 'https://ticktick.com',
        'sec-fetch-site': 'same-site',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty',
        'referer': 'https://ticktick.com/',
        'accept-language': 'es,en-US;q=0.9,en;q=0.8'
    }

Console Output:

<Response [200]>
<Response [200]>
<Response [200]>
<Response [401]>
Traceback (most recent call last):
  File "/Users/josephroberts/localdev/task-sync-db/test.py", line 21, in <module>
    groceries = tt.task.create(local_task)   # Actually create the task
  File "/usr/local/lib/python3.9/site-packages/ticktick/managers/tasks.py", line 147, in create
    response = self._client.http_post(url=url, json=task, headers=self.oauth_headers)
  File "/usr/local/lib/python3.9/site-packages/ticktick/api.py", line 206, in http_post
    self.check_status_code(response, 'Could Not Complete Request')
  File "/usr/local/lib/python3.9/site-packages/ticktick/api.py", line 135, in check_status_code
    raise RuntimeError(error_message)
RuntimeError: Could Not Complete Request

Lol, I tried deleting the .token-oauth file. @vibalcam's solution works now.

Lol, I tried deleting the .token-oauth file. @vibalcam's solution works now.

@broskees
My bad, forgot to mention that after changing that I had to delete and recreate the .token-oauth. Not sure why to be honest.

Unfortunately for me, @vibalcam solution did not work :(.

Unfortunately for me, @vibalcam solution did not work :(.

Try to follow the steps I took:

  1. Log into TickTick web and, using your browser's inspector, capture the http login request.
  2. Copy the headers to a app capable of doing API calls (I used PostMan). The easiest way I found was to export the http request login and import it into Postman.
  3. Test with Postman if it works
  4. Export the headers to Python
  5. Delete and regenerate TickTick's .token-oauth

Unfortunately for me, @vibalcam solution did not work :(.

Try to follow the steps I took:

  1. Log into TickTick web and, using your browser's inspector, capture the http login request.
  2. Copy the headers to a app capable of doing API calls (I used PostMan). The easiest way I found was to export the http request login and import it into Postman.
  3. Test with Postman if it works
  4. Export the headers to Python

Appreciate the input on this. I have tried these steps but am still getting the similar access_denied response despite all attempted OAUTH2 variations on headers.

I have tried those headers and also my own from a successful authentication to the ticktick webapp:
HEADERS = {'authority':'api.ticktick.com',
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36',
'x-requested-with':'XMLHttpRequest',
'x-device':'{...}',
'content-type':'application/json',
'accept':'/',
'sec-gpc':'1',
'origin':'https://ticktick.com',
'sec-fetch-site':'same-site',
'sec-fetch-mode':'cors',
'sec-fetch-dest':'empty',
'referer':'https://ticktick.com/'}

Noting but unsure of relevancy that I cannot achieve token generation even through manual request to the auth url through browser. Always returning access denied despite succesful POST request to confirm acceptance. And additionally, while in the documentation the access states are displayed to be accepted with an account image this does not appear correctly in all scenarios.

Wonder if you have any insight on other factors or code you changed to potentially successfully authenticate?

I forgot to mention that you have to regenerate .token-oauth.

If it does not allow you to regenerate the token, make sure you the code in http_post and http_get to:

    if 'headers' not in kwargs:
        response = self._session.post(url, headers=self.HEADERS, **kwargs)
    else:
        kwargs['headers'].update(self.HEADERS)
        response = self._session.post(url, **kwargs)
    self.check_status_code(response, 'Could Not Complete Request')

    try:
        return response.json()
    except ValueError:
        return response.text

This just makes sure that no other headers were being used.

I forgot to mention that you have to regenerate .token-oauth.

If it does not allow you to regenerate the token, make sure you the code in http_post and http_get to:

    if 'headers' not in kwargs:
        response = self._session.post(url, headers=self.HEADERS, **kwargs)
    else:
        kwargs['headers'].update(self.HEADERS)
        response = self._session.post(url, **kwargs)
    self.check_status_code(response, 'Could Not Complete Request')

    try:
        return response.json()
    except ValueError:
        return response.text

This just makes sure that no other headers were being used.

Hmm, not to be requesting support but interestingly I have no .token-oauth file generated, including a message in STDOUT that "Cache could not be read at: .token-oauth" which I had assumed was standard behavior if no valid token had been generated in past. If not, my issues may be differing.

To note I also applied the code mentioned but again seem to be experiencing different behavior, when you authenticate in this manner, does the browser window correctly display the requested permissions and your username? As it does in the documentation but not for mine. Somehow suggesting my request may be malformed if this is the case.

EDIT: Not sure if can confirm but potentially my issue is different from that of @vibalcam but looks the same as @eduardocopat . In that no variation of the authentication URL produces a code which can be later used to generate the token. always that authentication error. Further debugging has shown that my issue is not seemingly with the HTTP_POST or GET code in the API.PY as these functions appear to be only used after the redirect through the browser (If my understanding is correct)? As such they are not typically seeming to be run in my case as the initial authentication fails. Interestingly, this is also the case for me on the Zapier and IFTTT integrations (not sure if these work for others) which would somehow suggest a device or account issue, despite trying variations of differing situations of both.

EDIT 2: It now appears to be working pending further testing at 1/3/2022. It is most likely considering these circumstances that this was some sort of strange account bug, or likely I sent a few failed authentication requests and it resulted in an IP ban on the server side. I am happy to delete my comments on this issue if the author prefers or can leave here incase somebody else encounters this and mistakes it as relevant to the bug.

Cache could not be read at: .token-oauth
Enter the URL you were redirected to: http://127.0.0.1:8080/?code=BANANA&state=None
Traceback (most recent call last):
  File "/Users/dkvdm/Projects/portfolio/test.py", line 4, in <module>
    auth_client = OAuth2(client_id="BANANANANANA",
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 136, in __init__
    self.get_access_token(check_cache=check_cache, check_env=env_key)
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 311, in get_access_token
    token_info = self._request_access_token()
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 225, in _request_access_token
    token_info = self._post(self.OBTAIN_TOKEN_URL, params=payload)
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 249, in _post
    raise RuntimeError("POST request could not be completed")
RuntimeError: POST request could not be completed

This is still causing issues for me, anybody else experiencing similar login issues?

Cache could not be read at: .token-oauth
Enter the URL you were redirected to: http://127.0.0.1:8080/?code=BANANA&state=None
Traceback (most recent call last):
  File "/Users/dkvdm/Projects/portfolio/test.py", line 4, in <module>
    auth_client = OAuth2(client_id="BANANANANANA",
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 136, in __init__
    self.get_access_token(check_cache=check_cache, check_env=env_key)
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 311, in get_access_token
    token_info = self._request_access_token()
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 225, in _request_access_token
    token_info = self._post(self.OBTAIN_TOKEN_URL, params=payload)
  File "/Users/dkvdm/.virtualenvs/portfolio/lib/python3.9/site-packages/ticktick/oauth2.py", line 249, in _post
    raise RuntimeError("POST request could not be completed")
RuntimeError: POST request could not be completed

This is still causing issues for me, anybody else experiencing similar login issues?

got the same POST request could not be completed Error. Have you fix it ?