Skip to content

Conversation

@denyshon
Copy link
Contributor

@denyshon denyshon commented Oct 23, 2025

When Instagram returns "feedback_required", "checkpoint_required" or "challenge_required", any further requests will be declined until the required action is performed manually. Those messages mean Instagram suspects us of being a bot, so we must stop producing more requests immediately in order not to increase the suspicions.

This PR makes get_json (the status_code 400 block) raise AbortDownloadException with a detailed explanation for Instagram "feedback_required"/"checkpoint_required"/"challenge_required" responses.

I've updated get_json's docstring, but the documentation may need to be rerendered.

Generally, the PR's (almost?) ready to be merged.

I would appreciate a review and some help with docs (if there's something else to be done). You should be able to make edits directly to my branch.

Provide handling for Instagram feedback_required/checkpoint_required/challenge_required responses.
@denyshon denyshon force-pushed the handle-inst-requirements branch from 4c61303 to ad4f478 Compare November 2, 2025 23:33
Copy link
Member

@aandergr aandergr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. However the implementation itself would introduce some issues which I mentioned in inline comments here.

Given that there already is a _response_error() to build an error message, what would you think about the following way to handle the 400 status code instead?

            if resp.status_code == 400:
                with suppress(json.decoder.JSONDecodeError):
                    if resp.json().get("message") in [
                        "feedback_required",
                        "checkpoint_required",
                        "challenge_required",
                    ]:
                        # Raise AbortDownloadException in case of substantial Instagram
                        # requirements to stop producing more requests
                        raise AbortDownloadException(self._response_error(resp))
                raise QueryReturnedBadRequestException(self._response_error(resp))

Comment on lines 454 to 457
try:
resp_json = resp.json()
except ValueError:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this try-except is written will just leave resp_json uninitialized when resp.json() fails, continuing with accesses to an uninitialized variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, you are right, there should have been else

if 'application/json' in content_type or not content_type:
try:
resp_json = resp.json()
except ValueError:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resp.json() would actually raise json.decoder.JSONDecodeError. Though it is a ValueError, catching JSONDecodeError would make that part of the code more explanatory regarding what exactly is the error we're looking out for.

Comment on lines 452 to 453
content_type = resp.headers.get('Content-Type')
if 'application/json' in content_type or not content_type:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no added value in explicitly checking the content type. I suggest to directly call resp.json() and handle the case that it fails with a JSONDecodeError.

resp_json = resp.json()
except ValueError:
pass
if 'feedback_required' in resp_json.get('message'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message is "feedback_required"/"checkpoint_required"/"challenge_required", not a superstring of it, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I'm actually not sure about that, so I would better stick to a superstring version

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just searched the issues to check on that (the message string is printed to the log), we have

So actually no superstrings, just the plain "feedback_required"/"checkpoint_required"/"challenge_required" strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, then your version should be totally fine. Sorry for misleading!

@denyshon
Copy link
Contributor Author

denyshon commented Nov 8, 2025

@aandergr Thank you for reviewing! I like your version, except for the assumption that "feedback_required"/"checkpoint_required"/"challenge_required" is the whole message content (I'm not sure about that, and even if it's true now, it may change in the future)

@denyshon
Copy link
Contributor Author

denyshon commented Nov 8, 2025

Would you like me to change the PR accordingly, or would you prefer committing by yourself?

@aandergr
Copy link
Member

aandergr commented Nov 8, 2025

Would you like me to change the PR accordingly, or would you prefer committing by yourself?

Thanks! I created a commit with the proposed change.

@aandergr aandergr merged commit b7ef306 into instaloader:master Nov 8, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants