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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: igmapper
3
- Version: 1.2.0
3
+ Version: 2.1
4
4
  Summary: Unofficial Instagram API to request available data
5
5
  Author: lucasoal
6
6
  License: MIT
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: igmapper
3
- Version: 1.2.0
3
+ Version: 2.1
4
4
  Summary: Unofficial Instagram API to request available data
5
5
  Author: lucasoal
6
6
  License: MIT
@@ -4,7 +4,6 @@ README.md
4
4
  pyproject.toml
5
5
  igmapper/__init__.py
6
6
  igmapper/client.py
7
- igmapper/models.py
8
7
  igmapper/session.py
9
8
  igmapper.egg-info/PKG-INFO
10
9
  igmapper.egg-info/SOURCES.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "igmapper"
7
- version = "1.2.0"
7
+ version = "2.1"
8
8
  description = "Unofficial Instagram API to request available data"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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