diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 265bea2..86d9e49 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,5 +20,8 @@ describe: - Further considerations - Is it based on master (bugfix), upcoming/v4.X (new feature), or another pull request? - Does it preserve backward compatibility and existing behavior? + - If generative AI was involved, ensure it follows our AI policy. + +(also see https://instaloader.github.io/contributing.html) --> diff --git a/LICENSE b/LICENSE index e620fd1..15a4a78 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2016-2020 Alexander Graf and André Koch-Kramer. +Copyright (c) 2016-2026 Alexander Graf, André Koch-Kramer and the Instaloader +contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/conf.py b/docs/conf.py index c9d2b2e..18de48a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,7 @@ sys.path.insert(0, os.path.abspath('..')) # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'Instaloader' -copyright = '2023, Alexander Graf and Andre Koch-Kramer' +copyright = '2016-2026, Alexander Graf, Andre Koch-Kramer and the Instaloader contributors' author = 'Alexander Graf and Andre Koch-Kramer' # -- General configuration --------------------------------------------------- diff --git a/docs/contributing.rst b/docs/contributing.rst index 0588c5f..3894f7d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -80,7 +80,7 @@ Improvements of the Instaloader source or its documentation can be proposed as a review, validate, or selectively merge them. - Be mindful of backward compatibility: changes should preserve existing intended - behavior whenever possible. + behavior whenever feasible. - All Pull Requests are analyzed by `Pylint `__ for error and syntax checking of the source and @@ -100,6 +100,19 @@ Improvements of the Instaloader source or its documentation can be proposed as a agrees that the change might be an improvement, or if you want to discuss your basic proposal, before working on a pull request. +AI Policy +--------- + +- If generative AI was used to create notable portions of a contribution, the + author must disclose which parts were AI-assisted (e.g. research, testing, + coding, or documentation). + +- The author must carefully review and understand the contribution before + submitting it. + +- If the maintainers need more effort to review the contribution than it took + to create it, please do not submit it. + Proposing Features ------------------ diff --git a/instaloader/nodeiterator.py b/instaloader/nodeiterator.py index ebdcf86..5a01af8 100644 --- a/instaloader/nodeiterator.py +++ b/instaloader/nodeiterator.py @@ -104,6 +104,7 @@ class NodeIterator(Iterator[T]): else: self._data = self._query() self._first_node: Optional[Dict] = None + self._first_item: Optional[T] = None self._is_first = is_first def _query(self, after: Optional[str] = None) -> Dict: @@ -156,10 +157,10 @@ class NodeIterator(Iterator[T]): item = self._node_wrapper(node) if self._is_first is not None: if self._is_first(item, self.first_item): - self._first_node = node + self._first_node, self._first_item = node, item else: if self._first_node is None: - self._first_node = node + self._first_node, self._first_item = node, item return item if self._data.get('page_info', {}).get('has_next_page'): query_response = self._query(self._data['page_info']['end_cursor']) @@ -206,7 +207,15 @@ class NodeIterator(Iterator[T]): .. versionchanged:: 4.9.2 What is considered the first item can be overridden. """ - return self._node_wrapper(self._first_node) if self._first_node is not None else None + if self._first_item is None: + # Restore _first_item from _first_node. Needed after thawing, since + # FrozenNodeIterator stores the node, not the item. + self._first_item = ( + self._node_wrapper(self._first_node) + if self._first_node is not None + else None + ) + return self._first_item @staticmethod def page_length() -> int: diff --git a/instaloader/structures.py b/instaloader/structures.py index deb077e..4e7cdee 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -881,19 +881,9 @@ class Post: if self.likes == 0: # Avoid doing additional requests if there are no comments return - likes_edges = self._field('edge_media_preview_like', 'edges') - if self.likes == len(likes_edges): - # If the Post's metadata already contains all likes, don't do GraphQL requests to obtain them - yield from (Profile(self._context, like['node']) for like in likes_edges) - return - yield from NodeIterator( - self._context, - '1cb6ec562846122743b61e492c85999f', - lambda d: d['data']['shortcode_media']['edge_liked_by'], - lambda n: Profile(self._context, n), - {'shortcode': self.shortcode}, - 'https://www.instagram.com/p/{0}/'.format(self.shortcode), - ) + data = self._context.get_json(f"api/v1/media/{self.mediaid}/likers/", {}) + for user in data["users"]: + yield Profile(self._context, user) @property def is_sponsored(self) -> bool: