igmapper 1.2.0__tar.gz → 2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {igmapper-1.2.0/igmapper.egg-info → igmapper-2.1}/PKG-INFO +1 -1
- igmapper-2.1/igmapper/client.py +172 -0
- igmapper-2.1/igmapper/session.py +50 -0
- {igmapper-1.2.0 → igmapper-2.1/igmapper.egg-info}/PKG-INFO +1 -1
- {igmapper-1.2.0 → igmapper-2.1}/igmapper.egg-info/SOURCES.txt +0 -1
- {igmapper-1.2.0 → igmapper-2.1}/pyproject.toml +1 -1
- igmapper-1.2.0/igmapper/client.py +0 -147
- igmapper-1.2.0/igmapper/models.py +0 -218
- igmapper-1.2.0/igmapper/session.py +0 -31
- {igmapper-1.2.0 → igmapper-2.1}/AUTHORS.md +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/LICENSE +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/README.md +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/igmapper/__init__.py +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/igmapper.egg-info/dependency_links.txt +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/igmapper.egg-info/requires.txt +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/igmapper.egg-info/top_level.txt +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/setup.cfg +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/tests/test_feed.py +0 -0
- {igmapper-1.2.0 → igmapper-2.1}/tests/test_profile.py +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
import subprocess
|
|
5
|
+
import urllib.parse
|
|
6
|
+
|
|
7
|
+
from .session import InstagramSession
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InstaClient:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
csrftoken,
|
|
14
|
+
ds_user_id,
|
|
15
|
+
fbdtsg,
|
|
16
|
+
lsd,
|
|
17
|
+
sessionid,
|
|
18
|
+
datr=None,
|
|
19
|
+
ig_did=None,
|
|
20
|
+
mid=None,
|
|
21
|
+
rur=None,
|
|
22
|
+
av=None,
|
|
23
|
+
proxy=None,
|
|
24
|
+
user_agent=None,
|
|
25
|
+
):
|
|
26
|
+
self.state = InstagramSession(
|
|
27
|
+
csrftoken=csrftoken,
|
|
28
|
+
ds_user_id=ds_user_id,
|
|
29
|
+
fbdtsg=fbdtsg,
|
|
30
|
+
lsd=lsd,
|
|
31
|
+
sessionid=sessionid,
|
|
32
|
+
datr=datr,
|
|
33
|
+
ig_did=ig_did,
|
|
34
|
+
mid=mid,
|
|
35
|
+
rur=rur,
|
|
36
|
+
av=av,
|
|
37
|
+
proxy=proxy,
|
|
38
|
+
user_agent=user_agent,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def _get_jazoest(self) -> str:
|
|
42
|
+
if not self.state.fbdtsg:
|
|
43
|
+
return "26033"
|
|
44
|
+
return "2" + str(sum(ord(c) for c in self.state.fbdtsg))
|
|
45
|
+
|
|
46
|
+
# def get_profile_id(self, username: str) -> str | None:
|
|
47
|
+
# feed_response = self.get_feed(username)
|
|
48
|
+
# edges = (
|
|
49
|
+
# feed_response.get("data", {})
|
|
50
|
+
# .get("xdt_api__v1__feed__user_timeline_graphql_connection", {})
|
|
51
|
+
# .get("edges", [])
|
|
52
|
+
# )
|
|
53
|
+
# try:
|
|
54
|
+
# for edge in edges:
|
|
55
|
+
# user = edge.get("node", {}).get("user", {})
|
|
56
|
+
# if user.get("username") == username:
|
|
57
|
+
# return user.get("id")
|
|
58
|
+
# except Exception as e:
|
|
59
|
+
# return e
|
|
60
|
+
|
|
61
|
+
def _get_profile_id_anonymous(self, username: str = None) -> str:
|
|
62
|
+
random_csrftoken = "".join(
|
|
63
|
+
random.choice(string.ascii_letters + string.digits) for _ in range(32)
|
|
64
|
+
)
|
|
65
|
+
# fmt:off
|
|
66
|
+
command = [
|
|
67
|
+
"curl", "--url", "https://www.instagram.com/ajax/bulk-route-definitions/",
|
|
68
|
+
"-H", "accept: */*", "-H", "accept-language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7", "-H", "content-type: application/x-www-form-urlencoded",
|
|
69
|
+
# "-b", f"csrftoken=hWv1HUEXWJXrjQJ0Y3nI39; datr=H2ihap7UBS1zV2kDiVjHShzu; ig_did=18259CA3-CC0A-46AA-B15E-57D46B9065FB; mid=aqFoIAAEAAGyDFhRNqnbKpwaNnSJ; wd=1077x958",
|
|
70
|
+
"-b", f"csrftoken={random_csrftoken}; datr=H2ihap7UBS1zV2kDiVjHShzu; ig_did=18259CA3-CC0A-46AA-B15E-57D46B9065FB; mid=aqFoIAAEAAGyDFhRNqnbKpwaNnSJ; wd=1077x958",
|
|
71
|
+
"-H", "origin: https://www.instagram.com", "-H", "priority: u=1, i", "-H", f"referer: https://www.instagram.com/{username}/", "-H", "sec-ch-prefers-color-scheme: dark",
|
|
72
|
+
"-H", 'sec-ch-ua: "Not=A?Brand";v="99", "Google Chrome";v="151", "Chromium";v="151"',
|
|
73
|
+
"-H", 'sec-ch-ua-full-version-list: "Not=A?Brand";v="99.0.0.0", "Google Chrome";v="151.0.7922.173", "Chromium";v="151.0.7922.173"',
|
|
74
|
+
"-H", "sec-ch-ua-mobile: ?0", "-H", 'sec-ch-ua-model: ""', "-H", 'sec-ch-ua-platform: "Linux"', "-H", 'sec-ch-ua-platform-version: ""',
|
|
75
|
+
"-H", "sec-fetch-dest: empty", "-H", "sec-fetch-mode: cors", "-H", "sec-fetch-site: same-origin",
|
|
76
|
+
"-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
77
|
+
"-H", "x-asbd-id: 359341", "-H", "x-fb-lsd: AdT9k8PRVpJsQi_dNcZjFumNFSk", "-H", "x-ig-d: www", "-H", "x-ig-max-touch-points: 0", "--data-raw",
|
|
78
|
+
f"route_urls[0]=%2F{username}%2F&routing_namespace=igx_www%24a%2487a091182d5bd65bcb043a2888004e09&__d=www&__user=0&__a=1&__req=3&__hs=20705.HYP%3Ainstagram_web_pkg.2.1...0&dpr=1&__ccg=EXCELLENT&__rev=1047079153&__s=zlpokx%3Ah5wh9a%3A34lli1&__hsi=7683537958032746721&__dyn=7xeUjG1mxu1syaxG4Vp41twpUnwgU7SbzEdF8vyUco2qwJyE1kUhw2nVE4W0qa321Rw8G11wBz81s8hwGxu786a3a1YwBgao6C1uwoE2swlo8od8-U2zxe2GewGw9a361qw8Xxm16wa-0oa2-azo7u3C2u2J0bS1LyUaUbGxK3R08-269wr84-6o5p389oed6goK10xKi2K7E5y4U158KmUhw4rwXyFEaVE461Hwj83KwRzk1jw&__csr=gpR5gniPFlQDftkv_ibJUAR64nhh9iBJzXGpliLVaF4eHZaiAiu699Bnl4DAGh2u9DHWBRZ6ajrXAiTlWmhlPisClJdqlJLvAHHmiuVp4WQuynQdDGrGF7VpXXBVpAXhECQ-maCDBACDgy7E-EqyFoK5ppUO9x6VVFVEhyqGSqdBmfyqxeVFKiiqWwLx2QqFFpoK9BgOeCyEGezUlhoGax-5VbmcyJ1ucGHwLw0x2Aw2kU3yw083-00ReoO2u08Vw2S8ja0fra0f_g9U5fc027C1oN4i5U2YwJwUw8ato0yG09Ww5QwBo-Eb4exV3q8FoK1Zgf446qby80nLBwjC01J8w0BGUiw0xmw2Yo0Oe&__hsdp=l156MT1H4RT1L9664FAAqiioDyk64ezknuh4osK2yMGaGA6UPe26i1cDg8Ae80D8ig2qxRaWmu9AxB48dz9EgK0hObx21LxG2u1cwAg8EcpES1nw_wSwj8S3e18x6322i1dx26E4KU1-bw8C0JE0Z-0cGwfW687-07to5y582iBlwtU0Ne2W0hKi1Nw5rw4cg0VW08Lwto2fwdKubJo6il0&__hblp=0hU5C3S0xo6679ErwXU4u7Elm9Cw-wfSp1CcnDypbwIz9EgKi19x23am1LxebBCK585qUqxq484O2dwyxd1Oqdxa14w_wAx61cCCwPwi8yrwMxe484SfgqwIwloaE6W3G1lg2pK1HxS0JE1lU8U-0BEnwfK1SwNwl80Jm0_EowvU0ghwsE1qU6K0KE5S2a3m58vAyEb8dplo7um0zE2Xw72wKwaW1IAwlUpw5rw4cg0VW2-0C84Hwca2e7UixW1mwEwpE2rw_DyXm1ABg&__sjsp=l156MT1H99ns9MhOhxx0GhF99yu9xGexjpAhxOUbhEng&__comet_req=7&lsd=AdT9k8PRVpJsQi_dNcZjFumNFSk&jazoest=22406&__spin_r=1047079153&__spin_b=trunk&__spin_t=1788963088&__crn=comet.igweb.PolarisLoggedOutDesktopWWWProfileRoute",
|
|
79
|
+
]
|
|
80
|
+
# fmt:on
|
|
81
|
+
|
|
82
|
+
result = subprocess.run(command, capture_output=True, text=True)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
if result.returncode == 0:
|
|
86
|
+
json_str = result.stdout.replace("for (;;);", "").strip()
|
|
87
|
+
data_json = json.loads(json_str)
|
|
88
|
+
return data_json["payload"]["payloads"][f"/{username}/"]["result"][
|
|
89
|
+
"exports"
|
|
90
|
+
]["rootView"]["props"]["id"]
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return e
|
|
93
|
+
|
|
94
|
+
def get_profile(self, username: str = None) -> dict | str:
|
|
95
|
+
|
|
96
|
+
userid = self._get_profile_id_anonymous(username)
|
|
97
|
+
|
|
98
|
+
vars_dict = {
|
|
99
|
+
"enable_integrity_filters": True,
|
|
100
|
+
"id": userid,
|
|
101
|
+
"__relay_internal__pv__PolarisCannesGuardianExperienceEnabledrelayprovider": True,
|
|
102
|
+
"__relay_internal__pv__PolarisCASB976ProfileEnabledrelayprovider": False,
|
|
103
|
+
"__relay_internal__pv__PolarisWebSchoolsEnabledrelayprovider": False,
|
|
104
|
+
"__relay_internal__pv__PolarisRepostsConsumptionEnabledrelayprovider": True,
|
|
105
|
+
"__relay_internal__pv__PolarisShortDramaEnabledrelayprovider": False,
|
|
106
|
+
}
|
|
107
|
+
variables = urllib.parse.quote(json.dumps(vars_dict))
|
|
108
|
+
|
|
109
|
+
# fmt:off
|
|
110
|
+
payload = f"av={self.state.av}&__d=www&__user=0&__a=1&__req=1&__hs=20703.HYP%3Ainstagram_web_pkg.2.1...0&dpr=1&__ccg=EXCELLENT&__rev=1046936532&__s=4jhz11%3Awaevfz%3A1snom8&__hsi=7682841949103505900&__dyn=7xe6E5q5U5ObxG4Vp41twpUnwgU7SbzEdF8vyUco38w5ux609vCwjE1EE2Cw8G11w6zx61vwoEcE2ygao38woE2swaO4U2zxe2GewGw9a361qw8W1uw2oEGdwtU662O0Lo6-3u2WErwfG1IwjU721IQp1yU426V8aUuwm8jw4kyVrx60hK3KaCwHwi84q2i0jK3mewkU&__csr=hD4MJkaNcRtOAOF5Aail_QX9QFLnIAEzhJuVESdbtrAQqrWjn8iml7m8JqAVKaxG7pozzebHSmT-iWKUXAgrz-V8Wiq9AKJaumWKmicULDCg_UhV9aK_xq7Aczo8ouy88UiKbGi2m3iFAFopwyxKdyECmdz8akuEgxqbwwy8OErxKdxe58bEK4UconzUyu00mxm4809oi3E0bOE0wVwEwEw1vq8wvA1cai8gKWm0jVwzz8Dw3g8x0wBw0UDw0hFm0fDwkUcUO0HQ0IEfk07GE&__hsdp=rNL6ygGpQDF5h4BY95BCVqmgW-QK9x6Fe8F7ggxe8zogKbg52FGF5uE8U2_w6kDU0qSg&__sjsp=rNL6ygGpQKh5h4BY95ze8mgVeQK9x6Fe8Kt124Uy2V0kaCGAlWwzw&__comet_req=7&fb_dtsg={self.state.fbdtsg}&jazoest=26033&lsd={self.state.lsd}&__spin_r=1046936532&__spin_b=trunk&__spin_t=1788801036&fb_api_caller_class=RelayModern&fb_api_req_friendly_name=PolarisProfilePageContentQuery&server_timestamps=true&variables={variables}&doc_id=28036671149327607"
|
|
111
|
+
command = ["curl", "--url", "https://www.instagram.com/api/graphql", "-H", "accept: */*", "-H", "accept-language: pt-BR,pt;q=0.9,en-GB;q=0.8,en;q=0.7", "-H", "content-type: application/x-www-form-urlencoded", "-b", self.state.cookies, "-H", "origin: https://www.instagram.com", "-H", "priority: u=1, i", "-H", "referer: https://www.instagram.com/", "-H", "sec-ch-prefers-color-scheme: dark", "-H", 'sec-ch-ua: "Not=A?Brand";v="99", "Google Chrome";v="151", "Chromium";v="151"', "-H", 'sec-ch-ua-full-version-list: "Not=A?Brand";v="99.0.0.0", "Google Chrome";v="151.0.7922.173", "Chromium";v="151.0.7922.173"', "-H", "sec-fetch-dest: empty", "-H", "sec-fetch-mode: cors", "-H", "sec-fetch-site: same-origin", "-H", f"user-agent: {self.state.user_agent}", "-H", "x-asbd-id: 359341", "-H", f"x-csrftoken: {self.state.csrftoken}", "-H", "x-fb-friendly-name: PolarisProfilePageContentQuery", "-H", f"x-fb-lsd: {self.state.lsd}", "-H", "x-ig-app-id: 936619743392459", "-H", "x-ig-max-touch-points: 0", "--data-raw", payload]
|
|
112
|
+
# fmt:on
|
|
113
|
+
|
|
114
|
+
if self.state.proxy:
|
|
115
|
+
command.extend(["-x", self.state.proxy])
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
result = subprocess.run(command, capture_output=True, text=True)
|
|
119
|
+
return json.loads(result.stdout)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
return e
|
|
122
|
+
|
|
123
|
+
def get_feed(self, username: str, after: str = None) -> dict:
|
|
124
|
+
data_payload = {
|
|
125
|
+
"count": 32,
|
|
126
|
+
"include_reel_media_seen_timestamp": True,
|
|
127
|
+
"include_relationship_info": True,
|
|
128
|
+
"latest_besties_reel_media": True,
|
|
129
|
+
"latest_reel_media": True,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if after:
|
|
133
|
+
doc_id = "39535953862670189"
|
|
134
|
+
friendly_name = "PolarisProfilePostsTabContentQuery_connection"
|
|
135
|
+
vars_dict = {
|
|
136
|
+
"after": after,
|
|
137
|
+
"before": None,
|
|
138
|
+
"data": data_payload,
|
|
139
|
+
"first": 12,
|
|
140
|
+
"include_multi_captions": True,
|
|
141
|
+
"last": None,
|
|
142
|
+
"username": username,
|
|
143
|
+
"__relay_internal__pv__PolarisMultiCaptionCarouselEnabledrelayprovider": True,
|
|
144
|
+
"__relay_internal__pv__PolarisShortDramaEnabledrelayprovider": False,
|
|
145
|
+
"__relay_internal__pv__PolarisReelsRecoDebugOverlayEnabledrelayprovider": False,
|
|
146
|
+
}
|
|
147
|
+
else:
|
|
148
|
+
doc_id = "38154989454116081"
|
|
149
|
+
friendly_name = "PolarisProfilePostsQuery"
|
|
150
|
+
vars_dict = {
|
|
151
|
+
"data": data_payload,
|
|
152
|
+
"username": username,
|
|
153
|
+
"__relay_internal__pv__PolarisMultiCaptionCarouselEnabledrelayprovider": True,
|
|
154
|
+
"__relay_internal__pv__PolarisShortDramaEnabledrelayprovider": False,
|
|
155
|
+
"__relay_internal__pv__PolarisReelsRecoDebugOverlayEnabledrelayprovider": False,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
variables = urllib.parse.quote(json.dumps(vars_dict))
|
|
159
|
+
|
|
160
|
+
# fmt:off
|
|
161
|
+
payload = f"av={self.state.av}&__d=www&__user=0&__a=1&__req=p&__hs=20705.HYP%3Ainstagram_web_pkg.2.1...0&dpr=1&__ccg=EXCELLENT&__rev=1047087785&__s=bvefq6%3A27wtxx%3A3we0wk&__hsi=7683573197772336830&__dyn=7xeUjG1mxu1syaxG4Vp41twpUnwgU7SbzEdF8vyUco2qwJyEiw50x609vCwjE1EEc87m0yE462mcw5Mx62G5UswoEcE7O2l0Fwqo5W1yw9O1lwxwQzXwae4UaEW2G0AEco5G0zK5o4q0HU1wEbUGdwtUeo9UaQ0Lo6-bwHwKG6Ufk0zU8oC1IwjUpwlAcwBwUQp1yU426V8aUuwm8jxK0-8KmUhw4rwXyFEaVE4616wAwj83KwRyrg5e&__csr=hA8gP1xMEOP8lNdqh1pRpQBZ7XLfeiGpfmBCtQIAJTV5QihpaHGVt9k9Apy9HXSyoSKaiylh4yaGWKgxjFZIy_Q_5qriTsBqb8AGeiy4OnUy8CmXhDzaFpGiKjVWjGAp4QbCGul9x2l5AALwFh9YwugOaxh5zrzVpo99GGQmeCUhGfJKqK4WUjAxirQ8zFGGmiVArCzeUPzkUycG5ECbxyilu9xt3VHU8poLKuFFAbAw0BeCw6UwkE2xwsUW01-Sw0dgO5Ugw2gE0HhG58980YQE0_Z3o40yxh00yVwR4UB2Xc0hK8yU20yEO8w2bE0BC0n62C7k22vAxR8QwS0JF44k4qChGw1rCi1Xo06RC02pmU4e0c4wtk0bRw6Aw2g8&__hsdp=g9Y7JkMnmx_oVWt5fEj4dOGgMrBmAAkFAfQAlFR1JzKUu88afzF8iwXGcUsgg819w851-U6dyeV8Obzt1xswKcBG48fU2AUa8Ki32U3cxK7Uaoco4OEiAwsoqwRx6u1PzUhwzwSw-xBa12wjU11u08Ew8C0Do0i-w_w5Jw1kC0aewqo8U2fCO0FwiU0Lm320hGE6K0ot054w2jo0Pi2q0x838lU7C0FFk&__hblp=1C6EhwnEeo98jwgFFHzECaqgC11wh8PzGLwyhVV5z89p9-i364bwd62KFK4peVk8GdQczEXUGmEgCUdEkDxO4U4iaBm2yVV9A5UgK19xG6EO3-6Uiz8at0Kwjaxai1fwxxG4U8qy9pU6WQm9x62eUcEfk7k3u9wjawYwSwSwmK0Sku0km0yo521cw4iyU4q320y83pw74w_wpE6m1WwBw3Ro1sU18U660L84K3m3eu6UvBwqpIwG5VFE4i1EwNwLw2aEc82DwsGz85ym0ot0em1uw2jo38g2owhK0W84S10x22q3e1gw9y2ZnwsFE2CBg&__sjsp=g9Y6kllc9MRq7Z3S8hjW4N3sGAc6VlF968p3Z95qpMixW0FU&__comet_req=7&fb_dtsg={self.state.fbdtsg}&jazoest=26543&lsd={self.state.lsd}&__spin_r=1047087785&__spin_b=trunk&__spin_t=1788971293&__crn=comet.igweb.PolarisProfilePostsTabRoute&fb_api_caller_class=RelayModern&fb_api_req_friendly_name={friendly_name}&server_timestamps=true&variables={variables}&doc_id={doc_id}"
|
|
162
|
+
command = ["curl", "--url", "https://www.instagram.com/graphql/query", "-H", "accept: */*", "-H", "accept-language: pt-BR,pt;q=0.9,en-GB;q=0.8,en;q=0.7", "-H", "content-type: application/x-www-form-urlencoded", "-b", self.state.cookies, "-H", "origin: https://www.instagram.com", "-H", "priority: u=1, i", "-H", f"referer: https://www.instagram.com/{username}/", "-H", "sec-ch-prefers-color-scheme: dark", "-H", 'sec-ch-ua: "Not=A?Brand";v="99", "Google Chrome";v="151", "Chromium";v="151"', "-H", 'sec-ch-ua-full-version-list: "Not=A?Brand";v="99.0.0.0", "Google Chrome";v="151.0.7922.173", "Chromium";v="151.0.7922.173"', "-H", "sec-fetch-dest: empty", "-H", "sec-fetch-mode: cors", "-H", "sec-fetch-site: same-origin", "-H", f"user-agent: {self.state.user_agent}", "-H", "x-asbd-id: 359341", "-H", "x-bloks-version-id: 394436feebb82fbc8bf09459d29e98a4182d7d9f4f36777d8278b409536b0803", "-H", f"x-csrftoken: {self.state.csrftoken}", "-H", f"x-fb-friendly-name: {friendly_name}", "-H", f"x-fb-lsd: {self.state.lsd}", "-H", "x-ig-app-id: 936619743392459", "-H", "x-ig-max-touch-points: 0", "-H", "x-root-field-name: xdt_api__v1__feed__user_timeline_graphql_connection", "--data-raw", payload]
|
|
163
|
+
# fmt:on
|
|
164
|
+
|
|
165
|
+
if self.state.proxy:
|
|
166
|
+
command.extend(["-x", self.state.proxy])
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
result = subprocess.run(command, capture_output=True, text=True)
|
|
170
|
+
return json.loads(result.stdout)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
return e
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class InstagramSession:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
csrftoken,
|
|
5
|
+
ds_user_id,
|
|
6
|
+
fbdtsg,
|
|
7
|
+
lsd,
|
|
8
|
+
sessionid,
|
|
9
|
+
datr=None,
|
|
10
|
+
ig_did=None,
|
|
11
|
+
mid=None,
|
|
12
|
+
rur=None,
|
|
13
|
+
av=None,
|
|
14
|
+
proxy=None,
|
|
15
|
+
user_agent=None,
|
|
16
|
+
):
|
|
17
|
+
self.csrftoken = csrftoken
|
|
18
|
+
self.ds_user_id = ds_user_id
|
|
19
|
+
self.fbdtsg = fbdtsg
|
|
20
|
+
self.lsd = lsd
|
|
21
|
+
self.sessionid = sessionid
|
|
22
|
+
self.datr = datr
|
|
23
|
+
self.ig_did = ig_did
|
|
24
|
+
self.mid = mid
|
|
25
|
+
self.rur = rur
|
|
26
|
+
self.av = av or ds_user_id
|
|
27
|
+
self.proxy = proxy
|
|
28
|
+
self.user_agent = user_agent or (
|
|
29
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
cookies = [
|
|
33
|
+
f"csrftoken={self.csrftoken}",
|
|
34
|
+
f"ds_user_id={self.ds_user_id}",
|
|
35
|
+
f"sessionid={self.sessionid}",
|
|
36
|
+
"ig_nrcb=1",
|
|
37
|
+
"ps_l=1",
|
|
38
|
+
"ps_n=1",
|
|
39
|
+
"wd=1108x958",
|
|
40
|
+
]
|
|
41
|
+
if self.datr:
|
|
42
|
+
cookies.append(f"datr={self.datr}")
|
|
43
|
+
if self.ig_did:
|
|
44
|
+
cookies.append(f"ig_did={self.ig_did}")
|
|
45
|
+
if self.mid:
|
|
46
|
+
cookies.append(f"mid={self.mid}")
|
|
47
|
+
if self.rur:
|
|
48
|
+
cookies.append(f"rur={self.rur}")
|
|
49
|
+
|
|
50
|
+
self.cookies = "; ".join(cookies)
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import subprocess
|
|
3
|
-
import urllib.parse
|
|
4
|
-
|
|
5
|
-
import requests
|
|
6
|
-
|
|
7
|
-
from .session import InstagramSession
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class InstaClient:
|
|
11
|
-
def __init__(self, csrftoken, ds_user_id, sessionid, proxy=None, use_curl=False):
|
|
12
|
-
self.state = InstagramSession(csrftoken, ds_user_id, sessionid, proxy=proxy)
|
|
13
|
-
self.use_curl = use_curl
|
|
14
|
-
self.csrftoken = csrftoken
|
|
15
|
-
|
|
16
|
-
def _execute_request(self, method, url, params=None, data=None, extra_headers=None):
|
|
17
|
-
headers = {}
|
|
18
|
-
if extra_headers:
|
|
19
|
-
headers.update(extra_headers)
|
|
20
|
-
|
|
21
|
-
if params:
|
|
22
|
-
url = f"{url}?{urllib.parse.urlencode(params)}"
|
|
23
|
-
|
|
24
|
-
if not self.use_curl:
|
|
25
|
-
return self.state.request_on_session(
|
|
26
|
-
method, url, data=data, headers=headers if headers else None
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
return self._curl_request(method, url, data=data, extra_headers=headers)
|
|
30
|
-
|
|
31
|
-
def _curl_request(self, method, url, data=None, extra_headers=None):
|
|
32
|
-
cookie_str = (
|
|
33
|
-
f"csrftoken={self.state.session.cookies.get('csrftoken')}; "
|
|
34
|
-
f"ds_user_id={self.state.session.cookies.get('ds_user_id')}; "
|
|
35
|
-
f"sessionid={self.state.session.cookies.get('sessionid')};"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
command = [
|
|
39
|
-
"curl",
|
|
40
|
-
"-X",
|
|
41
|
-
method,
|
|
42
|
-
url,
|
|
43
|
-
"-H",
|
|
44
|
-
f"cookie: {cookie_str}",
|
|
45
|
-
"-H",
|
|
46
|
-
f"x-ig-app-id: {self.state.xigappid}",
|
|
47
|
-
"-H",
|
|
48
|
-
f"x-csrftoken: {self.state.session.cookies.get('csrftoken')}",
|
|
49
|
-
"-sS",
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
if extra_headers:
|
|
53
|
-
for k, v in extra_headers.items():
|
|
54
|
-
command.extend(["-H", f"{k}: {v}"])
|
|
55
|
-
|
|
56
|
-
if data:
|
|
57
|
-
body = urllib.parse.urlencode(data) if isinstance(data, dict) else str(data)
|
|
58
|
-
command.extend(["--data-raw", body])
|
|
59
|
-
|
|
60
|
-
if self.state.session.proxies.get("https"):
|
|
61
|
-
command.extend(["-x", self.state.session.proxies["https"]])
|
|
62
|
-
|
|
63
|
-
result = subprocess.run(command, capture_output=True, text=True)
|
|
64
|
-
|
|
65
|
-
class MockResponse:
|
|
66
|
-
def __init__(self, text, status_code):
|
|
67
|
-
self.text = text
|
|
68
|
-
self.status_code = status_code
|
|
69
|
-
|
|
70
|
-
def json(self):
|
|
71
|
-
try:
|
|
72
|
-
return json.loads(self.text)
|
|
73
|
-
except Exception:
|
|
74
|
-
return {}
|
|
75
|
-
|
|
76
|
-
status = 200 if result.returncode == 0 else 500
|
|
77
|
-
return MockResponse(result.stdout, status)
|
|
78
|
-
|
|
79
|
-
def get_profile_info(self, username: str, lsd=None, doc_id=None):
|
|
80
|
-
# 1º - FUNCIONA COM TODOS PERFIS, MENOS BUSINESS
|
|
81
|
-
url = "https://www.instagram.com/api/v1/users/web_profile_info/"
|
|
82
|
-
res = self._execute_request("GET", url, params={"username": username})
|
|
83
|
-
|
|
84
|
-
if res.status_code == 200:
|
|
85
|
-
data = res.json()
|
|
86
|
-
if data.get("status") != "fail" and data.get("data", {}).get("user"):
|
|
87
|
-
return data
|
|
88
|
-
|
|
89
|
-
# 2º - USANDO GRAPHQL (caso o 1 dê errado, pode ser perfil business)
|
|
90
|
-
user_id = self.get_feed(username)["user"].get("pk", None)
|
|
91
|
-
url = "https://www.instagram.com/api/graphql"
|
|
92
|
-
headers = {
|
|
93
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
94
|
-
"x-csrftoken": self.csrftoken,
|
|
95
|
-
"x-fb-lsd": lsd,
|
|
96
|
-
}
|
|
97
|
-
payload_vars = {
|
|
98
|
-
"enable_integrity_filters": True,
|
|
99
|
-
"id": user_id,
|
|
100
|
-
"__relay_internal__pv__PolarisCASB976ProfileEnabledrelayprovider": False,
|
|
101
|
-
"__relay_internal__pv__PolarisCannesGuardianExperienceEnabledrelayprovider": True,
|
|
102
|
-
"__relay_internal__pv__PolarisLongformEnabledrelayprovider": False,
|
|
103
|
-
"__relay_internal__pv__PolarisRepostsConsumptionEnabledrelayprovider": True,
|
|
104
|
-
"__relay_internal__pv__PolarisShortDramaEnabledrelayprovider": False,
|
|
105
|
-
"__relay_internal__pv__PolarisWebSchoolsEnabledrelayprovider": False,
|
|
106
|
-
}
|
|
107
|
-
payload = urllib.parse.urlencode(
|
|
108
|
-
{"doc_id": doc_id, "lsd": lsd, "variables": json.dumps(payload_vars)}
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
res = requests.post(url, headers=headers, data=payload)
|
|
112
|
-
|
|
113
|
-
if res.status_code == 200:
|
|
114
|
-
data = res.json()
|
|
115
|
-
if data.get("status") != "fail":
|
|
116
|
-
return data
|
|
117
|
-
|
|
118
|
-
return None
|
|
119
|
-
|
|
120
|
-
def get_feed(self, username: str, max_id: str = ""):
|
|
121
|
-
url = f"https://www.instagram.com/api/v1/feed/user/{username}/username/"
|
|
122
|
-
params = {"count": 33, "max_id": max_id}
|
|
123
|
-
|
|
124
|
-
res = self._execute_request("GET", url, params=params)
|
|
125
|
-
|
|
126
|
-
if res.status_code == 200:
|
|
127
|
-
data = res.json()
|
|
128
|
-
if data.get("status") != "fail":
|
|
129
|
-
return data
|
|
130
|
-
|
|
131
|
-
return None
|
|
132
|
-
|
|
133
|
-
def get_comments(self, media_id: str, next_min_id: str = None):
|
|
134
|
-
url = f"https://www.instagram.com/api/v1/media/{media_id}/comments/"
|
|
135
|
-
|
|
136
|
-
params = {"can_support_threading": "true"}
|
|
137
|
-
if next_min_id:
|
|
138
|
-
params["min_id"] = next_min_id
|
|
139
|
-
|
|
140
|
-
res = self._execute_request("GET", url, params=params)
|
|
141
|
-
|
|
142
|
-
if res.status_code == 200:
|
|
143
|
-
data = res.json()
|
|
144
|
-
if data.get("status") != "fail":
|
|
145
|
-
return data
|
|
146
|
-
|
|
147
|
-
return None
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
4
|
-
|
|
5
|
-
from pydantic import BaseModel, Field, HttpUrl
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class ProfileData(BaseModel):
|
|
9
|
-
id: str
|
|
10
|
-
username: str
|
|
11
|
-
full_name: Optional[str] = None
|
|
12
|
-
biography: Optional[str] = None
|
|
13
|
-
profile_pic_url: Optional[HttpUrl] = None
|
|
14
|
-
|
|
15
|
-
is_private: bool = False
|
|
16
|
-
is_verified: bool = False
|
|
17
|
-
is_business_account: bool = False
|
|
18
|
-
is_professional_account: bool = False
|
|
19
|
-
|
|
20
|
-
follower_count: int = 0
|
|
21
|
-
following_count: int = 0
|
|
22
|
-
total_posts: int = 0
|
|
23
|
-
highlight_reel_count: int = 0
|
|
24
|
-
mutual_followers: int = 0
|
|
25
|
-
|
|
26
|
-
category_name: Optional[str] = None
|
|
27
|
-
business_category_name: Optional[str] = None
|
|
28
|
-
should_show_category: Optional[bool] = None
|
|
29
|
-
|
|
30
|
-
has_clips: Optional[bool] = None
|
|
31
|
-
has_guides: Optional[bool] = None
|
|
32
|
-
has_channel: Optional[bool] = None
|
|
33
|
-
|
|
34
|
-
external_url: Optional[HttpUrl] = None
|
|
35
|
-
bio_links: List[Dict[str, Any]] = []
|
|
36
|
-
|
|
37
|
-
business_address: Optional[Dict[str, Any]] = None
|
|
38
|
-
|
|
39
|
-
should_show_public_contacts: Optional[bool] = None
|
|
40
|
-
business_email: Optional[str] = None
|
|
41
|
-
business_phone_number: Optional[str] = None
|
|
42
|
-
|
|
43
|
-
@classmethod
|
|
44
|
-
def parse_instagram_json(cls, data: dict):
|
|
45
|
-
if not isinstance(data, dict):
|
|
46
|
-
return None
|
|
47
|
-
|
|
48
|
-
user = (
|
|
49
|
-
data.get("data", {}).get("user")
|
|
50
|
-
or data.get("data", {}).get("xdt_api__v1__profile_header")
|
|
51
|
-
or data.get("user")
|
|
52
|
-
)
|
|
53
|
-
if not isinstance(user, dict) or not user:
|
|
54
|
-
return None
|
|
55
|
-
|
|
56
|
-
business_address_raw = user.get("business_address_json")
|
|
57
|
-
parsed_address = None
|
|
58
|
-
if business_address_raw:
|
|
59
|
-
if isinstance(business_address_raw, str):
|
|
60
|
-
try:
|
|
61
|
-
parsed_address = json.loads(business_address_raw)
|
|
62
|
-
except Exception:
|
|
63
|
-
parsed_address = None
|
|
64
|
-
elif isinstance(business_address_raw, dict):
|
|
65
|
-
parsed_address = business_address_raw
|
|
66
|
-
|
|
67
|
-
follower_count = (
|
|
68
|
-
user.get("edge_followed_by", {}).get("count")
|
|
69
|
-
if "edge_followed_by" in user
|
|
70
|
-
else user.get("follower_count", 0)
|
|
71
|
-
)
|
|
72
|
-
following_count = (
|
|
73
|
-
user.get("edge_follow", {}).get("count") if "edge_follow" in user else user.get("following_count", 0)
|
|
74
|
-
)
|
|
75
|
-
total_posts = (
|
|
76
|
-
user.get("edge_owner_to_timeline_media", {}).get("count")
|
|
77
|
-
if "edge_owner_to_timeline_media" in user
|
|
78
|
-
else user.get("media_count", 0)
|
|
79
|
-
)
|
|
80
|
-
mutual_followers = (
|
|
81
|
-
user.get("edge_mutual_followed_by", {}).get("count")
|
|
82
|
-
if "edge_mutual_followed_by" in user
|
|
83
|
-
else user.get("mutual_followers_count", 0)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
profile_pic = user.get("profile_pic_url_hd") or user.get("profile_pic_url")
|
|
87
|
-
user_id = str(user.get("id") or user.get("pk") or "")
|
|
88
|
-
|
|
89
|
-
return cls(
|
|
90
|
-
id=user_id,
|
|
91
|
-
username=user.get("username", ""),
|
|
92
|
-
full_name=user.get("full_name"),
|
|
93
|
-
biography=user.get("biography"),
|
|
94
|
-
profile_pic_url=profile_pic,
|
|
95
|
-
is_private=user.get("is_private", False),
|
|
96
|
-
is_verified=user.get("is_verified", False),
|
|
97
|
-
is_business_account=user.get("is_business_account", False),
|
|
98
|
-
is_professional_account=user.get("is_professional_account", False),
|
|
99
|
-
follower_count=follower_count or 0,
|
|
100
|
-
following_count=following_count or 0,
|
|
101
|
-
total_posts=total_posts or 0,
|
|
102
|
-
highlight_reel_count=user.get("highlight_reel_count", 0),
|
|
103
|
-
mutual_followers=mutual_followers or 0,
|
|
104
|
-
category_name=user.get("category_name") or user.get("category"),
|
|
105
|
-
business_category_name=user.get("business_category_name"),
|
|
106
|
-
should_show_category=user.get("should_show_category"),
|
|
107
|
-
has_clips=user.get("has_clips"),
|
|
108
|
-
has_guides=user.get("has_guides"),
|
|
109
|
-
has_channel=user.get("has_channel"),
|
|
110
|
-
external_url=user.get("external_url"),
|
|
111
|
-
bio_links=user.get("bio_links", []),
|
|
112
|
-
business_address=parsed_address,
|
|
113
|
-
should_show_public_contacts=user.get("should_show_public_contacts"),
|
|
114
|
-
business_email=user.get("public_email") or user.get("business_email"),
|
|
115
|
-
business_phone_number=user.get("public_phone_number") or user.get("business_phone_number"),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class FeedData(BaseModel):
|
|
120
|
-
post_id: Optional[str] = Field(default=None, alias="id")
|
|
121
|
-
pk: Optional[str] = None
|
|
122
|
-
shortcode: Optional[str] = Field(default=None, alias="code")
|
|
123
|
-
created_at: Optional[datetime] = None
|
|
124
|
-
caption: Optional[str] = None
|
|
125
|
-
media_url: Optional[HttpUrl] = None
|
|
126
|
-
media_type: Optional[int] = None
|
|
127
|
-
like_count: Optional[int] = 0
|
|
128
|
-
comment_count: Optional[int] = 0
|
|
129
|
-
view_count: Optional[int] = None
|
|
130
|
-
carousel_media_count: Optional[int] = 0
|
|
131
|
-
|
|
132
|
-
posts: Optional[List["FeedData"]] = None
|
|
133
|
-
next_max_id: Optional[str] = None
|
|
134
|
-
num_results: Optional[int] = None
|
|
135
|
-
more_available: Optional[bool] = None
|
|
136
|
-
|
|
137
|
-
@classmethod
|
|
138
|
-
def parse_item(cls, item: dict):
|
|
139
|
-
ts = item.get("taken_at") or (item.get("device_timestamp", 0) / 1_000_000)
|
|
140
|
-
|
|
141
|
-
caption_dict = item.get("caption")
|
|
142
|
-
caption_text = caption_dict.get("text") if caption_dict else None
|
|
143
|
-
|
|
144
|
-
candidates = item.get("image_versions2", {}).get("candidates", [])
|
|
145
|
-
best_image = candidates[0].get("url") if candidates else None
|
|
146
|
-
|
|
147
|
-
return cls(
|
|
148
|
-
id=item.get("id"),
|
|
149
|
-
pk=item.get("pk"),
|
|
150
|
-
code=item.get("code"),
|
|
151
|
-
created_at=datetime.fromtimestamp(ts) if ts else None,
|
|
152
|
-
caption=caption_text,
|
|
153
|
-
media_url=best_image,
|
|
154
|
-
media_type=item.get("media_type"),
|
|
155
|
-
like_count=item.get("like_count", 0),
|
|
156
|
-
comment_count=item.get("comment_count", 0),
|
|
157
|
-
view_count=item.get("view_count"),
|
|
158
|
-
carousel_media_count=item.get("carousel_media_count", 0),
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
class CommentsData(BaseModel):
|
|
163
|
-
comment_id: Optional[str] = Field(default=None, alias="pk")
|
|
164
|
-
media_id: Optional[str] = None
|
|
165
|
-
text: Optional[str] = None
|
|
166
|
-
created_at: Optional[datetime] = None
|
|
167
|
-
like_count: Optional[int] = Field(default=0, alias="comment_like_count")
|
|
168
|
-
content_type: Optional[str] = None
|
|
169
|
-
status: Optional[str] = None
|
|
170
|
-
is_ranked_comment: Optional[bool] = None
|
|
171
|
-
is_edited: Optional[bool] = None
|
|
172
|
-
|
|
173
|
-
user_id: Optional[str] = None
|
|
174
|
-
username: Optional[str] = None
|
|
175
|
-
full_name: Optional[str] = None
|
|
176
|
-
profile_pic_url: Optional[HttpUrl] = None
|
|
177
|
-
is_private: Optional[bool] = None
|
|
178
|
-
is_verified: Optional[bool] = None
|
|
179
|
-
|
|
180
|
-
comments: Optional[List["CommentsData"]] = None
|
|
181
|
-
next_max_id: Optional[str] = None
|
|
182
|
-
num_results: Optional[int] = None
|
|
183
|
-
more_available: Optional[bool] = None
|
|
184
|
-
|
|
185
|
-
@classmethod
|
|
186
|
-
def parse_item(cls, item: dict):
|
|
187
|
-
ts = item.get("created_at")
|
|
188
|
-
|
|
189
|
-
user = item.get("user", {})
|
|
190
|
-
|
|
191
|
-
return cls(
|
|
192
|
-
pk=item.get("pk"),
|
|
193
|
-
media_id=item.get("media_id"),
|
|
194
|
-
text=item.get("text"),
|
|
195
|
-
created_at=datetime.fromtimestamp(ts) if ts else None,
|
|
196
|
-
comment_like_count=item.get("comment_like_count", 0),
|
|
197
|
-
content_type=item.get("content_type"),
|
|
198
|
-
status=item.get("status"),
|
|
199
|
-
is_ranked_comment=item.get("is_ranked_comment"),
|
|
200
|
-
is_edited=item.get("is_edited"),
|
|
201
|
-
user_id=user.get("id"),
|
|
202
|
-
username=user.get("username"),
|
|
203
|
-
full_name=user.get("full_name"),
|
|
204
|
-
profile_pic_url=user.get("profile_pic_url"),
|
|
205
|
-
is_private=user.get("is_private"),
|
|
206
|
-
is_verified=user.get("is_verified"),
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
@classmethod
|
|
210
|
-
def parse_response(cls, data: dict):
|
|
211
|
-
comments_data = data.get("comments", [])
|
|
212
|
-
|
|
213
|
-
return cls(
|
|
214
|
-
comments=[cls.parse_item(item) for item in comments_data],
|
|
215
|
-
next_max_id=data.get("next_max_id"),
|
|
216
|
-
num_results=data.get("num_results"),
|
|
217
|
-
more_available=data.get("more_available"),
|
|
218
|
-
)
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class InstagramSession:
|
|
5
|
-
def __init__(self, csrftoken, ds_user_id, sessionid, xigappid="936619743392459", proxy=None):
|
|
6
|
-
self.session = requests.Session()
|
|
7
|
-
self.xigappid = xigappid
|
|
8
|
-
self.proxy = proxy
|
|
9
|
-
|
|
10
|
-
self.session.cookies.set("csrftoken", csrftoken)
|
|
11
|
-
self.session.cookies.set("ds_user_id", ds_user_id)
|
|
12
|
-
self.session.cookies.set("sessionid", sessionid)
|
|
13
|
-
|
|
14
|
-
self.default_headers = {
|
|
15
|
-
"x-ig-app-id": self.xigappid,
|
|
16
|
-
"x-csrftoken": csrftoken,
|
|
17
|
-
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
|
18
|
-
"accept": "*/*",
|
|
19
|
-
"accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
20
|
-
}
|
|
21
|
-
self.session.headers.update(self.default_headers)
|
|
22
|
-
|
|
23
|
-
if proxy:
|
|
24
|
-
self.session.proxies = {"http": proxy, "https": proxy}
|
|
25
|
-
|
|
26
|
-
def get_cookie_string(self):
|
|
27
|
-
c = self.session.cookies.get_dict()
|
|
28
|
-
return "; ".join([f"{k}={v}" for k, v in c.items()])
|
|
29
|
-
|
|
30
|
-
def request_on_session(self, method, url, **kwargs):
|
|
31
|
-
return self.session.request(method, url, timeout=10, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|