Skip to content

Commit 16fb872

Browse files
committed
🔃wip: add follow mass feature
1 parent 75a1db0 commit 16fb872

File tree

4 files changed

+190
-63
lines changed

4 files changed

+190
-63
lines changed

backend/routers/ads_management.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import List
1111
from fastapi import APIRouter, Depends
1212
from pydantic import BaseModel
13-
from utils import get_vinted_headers
13+
from utils import execute_request, get_vinted_headers
1414
from constants import API_URL
1515
from sqlmodel import Session, select
1616
from config.models import User, get_session
@@ -113,7 +113,7 @@ async def refresh_ads(
113113
while True:
114114
user = session.exec(select(User).where(User.id == 1)).first()
115115
url = f"{API_URL}wardrobe/{user.userId}/items?page={page}&per_page=20&order=revelance"
116-
response = requests.get(url, headers=headers)
116+
response = execute_request("get", url, headers)
117117

118118
if response.status_code != 200:
119119
break
@@ -193,7 +193,7 @@ async def delete_sold_items(
193193
while True:
194194
user = session.exec(select(User).where(User.id == 1)).first()
195195
url = f"{API_URL}wardrobe/{user.userId}/items?page={page}&per_page=20&order=revelance"
196-
response = requests.get(url, headers=headers)
196+
response = execute_request("get", url, headers)
197197
nb_items_deleted = 0
198198

199199
if response.status_code != 200:
@@ -215,8 +215,8 @@ async def delete_sold_items(
215215
sleep(30)
216216
nb_items_deleted = 0
217217

218-
response = requests.delete(
219-
f"{API_URL}items/{item['id']}/delete", headers=headers
218+
response = execute_request(
219+
"delete", f"{API_URL}items/{item['id']}/delete", headers
220220
)
221221
if response.status_code == 200:
222222
nb_items_deleted += 1
@@ -236,7 +236,7 @@ async def delete_all_ads(
236236
while True:
237237
user = session.exec(select(User).where(User.id == 1)).first()
238238
url = f"{API_URL}wardrobe/{user.userId}/items?page={page}&per_page=20&order=revelance"
239-
response = requests.get(url, headers=headers)
239+
response = execute_request("get", url, headers)
240240
nb_items_deleted = 0
241241

242242
if response.status_code != 200:
@@ -254,8 +254,8 @@ async def delete_all_ads(
254254
sleep(30)
255255
nb_items_deleted = 0
256256

257-
response = requests.delete(
258-
f"{API_URL}items/{item['id']}/delete", headers=headers
257+
response = execute_request(
258+
"delete", f"{API_URL}items/{item['id']}/delete", headers
259259
)
260260
if response.status_code == 200:
261261
nb_items_deleted += 1

backend/routers/follow_mass.py

Lines changed: 108 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import re
2-
import requests
32
import time
43
from fastapi import HTTPException, APIRouter, Depends
5-
from utils import get_vinted_headers
4+
from utils import execute_request, get_vinted_headers
65
from constants import API_URL
76
from sqlmodel import Session, select, delete
87
from config.models import FollowedUser, User, get_session
@@ -24,9 +23,10 @@ async def backup_followed_users(
2423
):
2524
user = session.exec(select(User).where(User.id == 1)).first()
2625
page = 1
27-
response = requests.get(
26+
response = execute_request(
27+
"get",
2828
f"{API_URL}users/{user.userId}/followed_users?page={page}&per_page=20",
29-
headers=headers,
29+
headers,
3030
)
3131

3232
if not response.status_code == 200:
@@ -38,9 +38,10 @@ async def backup_followed_users(
3838
nb_pages = pagination["total_pages"]
3939

4040
while page <= nb_pages:
41-
response = requests.get(
41+
response = execute_request(
42+
"get",
4243
f"{API_URL}users/{user.userId}/followed_users?page={page}&per_page=20",
43-
headers=headers,
44+
headers,
4445
)
4546
for user_followed in response.json()["users"]:
4647
followed_user = session.exec(
@@ -60,31 +61,82 @@ async def backup_followed_users(
6061
return {"message": "Users followed backuped"}
6162

6263

63-
def toggle_follow(user_id: int, headers: dict):
64+
def toggle_follow(user_id: int, headers: dict) -> int:
6465
url = f"{API_URL}followed_users/toggle"
6566
data = {"user_id": user_id}
66-
response = requests.post(url, headers=headers, json=data)
67-
return response.json()
67+
response = execute_request("post", url, headers, data)
68+
print(
69+
f"[RESULT] user_id: {user_id} CODE: {response.status_code} | REASON: {response.reason} | text: {response.text}"
70+
)
71+
return response.status_code
6872

6973

7074
@router.get("/recover-followed-users")
7175
async def recover_followed_users(
7276
headers: dict = Depends(get_vinted_headers), session: Session = Depends(get_session)
7377
):
7478
user = session.exec(select(User).where(User.id == 1)).first()
79+
80+
nb_users_unfollowed = unfollow_all_users(user.userId, headers)
81+
nb_users_recovered = follow_all_users(user.userId, session, headers)
82+
83+
return {
84+
"message": "Users followed recovered",
85+
"nbUsersUnfollowed": nb_users_unfollowed,
86+
"nbUsersRecovered": nb_users_recovered,
87+
}
88+
89+
90+
def unfollow_all_users(user_id: int, headers: dict) -> int:
91+
nb_users_unfollowed = 0
92+
page = 1
93+
long_pause = 0
94+
short_pause = 0
95+
96+
response = execute_request(
97+
"get",
98+
f"{API_URL}users/{user_id}/following?page={page}&per_page=20",
99+
headers=headers,
100+
)
101+
nb_pages = response.json()["pagination"]["total_pages"]
102+
103+
while page <= nb_pages:
104+
105+
if long_pause == 100:
106+
time.sleep(60)
107+
long_pause = 0
108+
if short_pause == 30:
109+
time.sleep(10)
110+
short_pause = 0
111+
112+
response = execute_request(
113+
"get",
114+
f"{API_URL}users/{user_id}/following?page={page}&per_page=20",
115+
headers=headers,
116+
)
117+
118+
for user in response.json()["users"]:
119+
status_code = toggle_follow(user["id"], headers)
120+
121+
if status_code == 200:
122+
nb_users_unfollowed += 1
123+
124+
long_pause += 1
125+
short_pause += 1
126+
127+
page += 1
128+
129+
return nb_users_unfollowed
130+
131+
132+
def follow_all_users(user_id: int, session: Session, headers: dict) -> int:
75133
followed_users = session.exec(
76-
select(FollowedUser).where(FollowedUser.userId == user.userId)
134+
select(FollowedUser).where(FollowedUser.userId == user_id)
77135
).all()
78136

79137
long_pause = 0
80138
short_pause = 0
81-
82-
if long_pause == 100:
83-
time.sleep(60)
84-
long_pause = 0
85-
if short_pause == 30:
86-
time.sleep(10)
87-
short_pause = 0
139+
nb_users_recovered = 0
88140

89141
for followed_user in followed_users:
90142
if long_pause == 10:
@@ -93,21 +145,50 @@ async def recover_followed_users(
93145
if short_pause == 5:
94146
time.sleep(10)
95147
short_pause = 0
96-
toggle_follow(followed_user.followedUserId, headers)
148+
149+
status_code = toggle_follow(followed_user.followedUserId, headers)
150+
151+
if status_code == 200:
152+
nb_users_recovered += 1
153+
97154
long_pause += 1
98155
short_pause += 1
99156

100-
return {"message": "Users followed recovered"}
101157

158+
@router.post("/scrap-vinted-user")
159+
async def scrap_vinted_user(
160+
scrap_vinted_user: ScrapVintedUser, headers: dict = Depends(get_vinted_headers)
161+
):
162+
id = re.search("\d+", scrap_vinted_user.urlSeller).group(0)
163+
page = 1
164+
nb_users_scraped = 0
165+
mode = "followers" if scrap_vinted_user.mode == "followers" else "following"
102166

103-
@router.get("/scrap-vinted-user")
104-
async def scrap_vinted_user(scrap_vinted_user: ScrapVintedUser):
105-
id = re.search("\d+", scrap_vinted_user.url_seller).group(0)
167+
response = execute_request(
168+
"get",
169+
f"{API_URL}users/{id}/{mode}?page={page}&per_page=20",
170+
headers=headers,
171+
)
172+
nb_pages = response.json()["pagination"]["total_pages"]
106173

107-
if scrap_vinted_user.mode == "followers":
108-
...
174+
while page <= nb_pages:
175+
response = execute_request(
176+
"get",
177+
f"{API_URL}users/{id}/{mode}?page={page}&per_page=20",
178+
headers=headers,
179+
)
180+
181+
for user in response.json()["users"]:
182+
print(f"Scraping user: {user['login']} | id: {user['id']}")
109183

110-
if scrap_vinted_user.mode == "following":
111-
...
184+
if user["is_favourite"] == False and user["id"] != id:
185+
status_code = toggle_follow(user["id"], headers)
186+
if status_code == 200:
187+
nb_users_scraped += 1
188+
189+
page += 1
112190

113-
return {"message": "Users scraped from vinted user"}
191+
return {
192+
"message": "Users scraped from vinted user",
193+
"nbUsersScraped": nb_users_scraped,
194+
}

backend/utils.py

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from config.models import User, get_session
77

88

9+
MAX_RETRIES = 3
10+
11+
912
def get_vinted_headers(session: Session = Depends(get_session)) -> dict:
1013
"""Returns headers for Vinted API requests"""
1114
auth = session.exec(select(User).order_by(User.id.desc())).first()
@@ -17,38 +20,80 @@ def get_vinted_headers(session: Session = Depends(get_session)) -> dict:
1720
)
1821

1922
if auth.expires_at and auth.expires_at < datetime.datetime.now():
20-
raise HTTPException(
21-
status_code=401, detail="Vinted token has expired. Please login again."
22-
)
23+
refresh_access_token(headers, session)
24+
auth = session.exec(select(User).order_by(User.id.desc())).first()
2325

24-
return {
26+
headers = {
2527
**BASE_HEADERS,
2628
"Cookie": f"access_token_web={auth.vinted_access_token}; refresh_token_web={auth.vinted_refresh_token}",
2729
}
2830

31+
i = 0
2932

30-
def validate_vinted_token(session: Session = Depends(get_session)) -> str:
31-
"""Validates the Vinted token from database"""
32-
auth = session.exec(select(User).order_by(User.id.desc())).first()
33+
while i < MAX_RETRIES:
34+
response = requests.get(f"{API_URL}users/countries", headers=headers)
3335

34-
if not auth:
35-
raise HTTPException(
36-
status_code=401,
37-
detail="No Vinted authentication found. Please login first.",
38-
)
36+
if response.status_code == 200:
37+
return headers
3938

40-
if auth.expires_at and auth.expires_at < datetime.datetime.now():
41-
raise HTTPException(
42-
status_code=401, detail="Vinted token has expired. Please login again."
39+
refresh_access_token(headers, session)
40+
auth = session.exec(select(User).order_by(User.id.desc())).first()
41+
headers["Cookie"] = (
42+
f"access_token_web={auth.vinted_access_token}; refresh_token_web={auth.vinted_refresh_token}"
4343
)
44+
i += 1
4445

45-
headers = get_vinted_headers(auth.vinted_access_token, auth.vinted_refresh_token)
46-
response = requests.get(f"{API_URL}users/countries", headers=headers)
46+
raise HTTPException(
47+
status_code=401,
48+
detail="Token refused by Vinted server. Please update your token.",
49+
)
4750

48-
if response.status_code != 200:
49-
raise HTTPException(
50-
status_code=401,
51-
detail="Token refused by Vinted server. Please update your token.",
52-
)
5351

54-
return auth.vinted_access_token
52+
def execute_request(
53+
method: str,
54+
url: str,
55+
headers: dict,
56+
data: dict = None,
57+
session: Session = Depends(get_session),
58+
) -> requests.Response:
59+
i = 0
60+
61+
while i < MAX_RETRIES:
62+
if method == "get":
63+
response = requests.get(url, headers=headers)
64+
elif method == "post":
65+
response = requests.post(url, headers=headers, json=data)
66+
elif method == "put":
67+
response = requests.put(url, headers=headers, json=data)
68+
elif method == "delete":
69+
response = requests.delete(url, headers=headers, json=data)
70+
71+
if response.status_code != 200:
72+
refresh_access_token(headers, session)
73+
74+
headers = get_vinted_headers()
75+
print(
76+
f"Status code: {response.status_code}",
77+
f"Reason: {response.reason}",
78+
f"data: {data}",
79+
f"url: {url}",
80+
)
81+
i += 1
82+
else:
83+
return response
84+
85+
raise HTTPException(
86+
status_code=401,
87+
detail="Token refused by Vinted server. Please update your token.",
88+
)
89+
90+
91+
def refresh_access_token(headers: dict, session: Session):
92+
response = execute_request(
93+
"post", f"https://www.vinted.fr/web/api/auth/refresh", headers
94+
)
95+
print("Token vient d'être rafraîchi", response.json()["access_token"][:-5])
96+
auth = session.exec(select(User).order_by(User.id.desc())).first()
97+
auth.vinted_access_token = response.json()["access_token"]
98+
session.add(auth)
99+
session.commit()

0 commit comments

Comments
 (0)