From 2192978f91879ae620b6a7b1622aef64b820636c Mon Sep 17 00:00:00 2001 From: Keredu Date: Sat, 25 May 2024 00:12:26 +0200 Subject: [PATCH 1/4] Limit on /search is not deterministic --- apps/api/src/controllers/search.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/api/src/controllers/search.ts b/apps/api/src/controllers/search.ts index d98c08d..a73c625 100644 --- a/apps/api/src/controllers/search.ts +++ b/apps/api/src/controllers/search.ts @@ -28,11 +28,13 @@ export async function searchHelper( const tbs = searchOptions.tbs ?? null; const filter = searchOptions.filter ?? null; + const num_results = searchOptions.limit ?? 7; + const num_results_buffer = Math.floor(num_results * 1.5); let res = await search({ query: query, advanced: advanced, - num_results: searchOptions.limit ?? 7, + num_results: num_results_buffer, tbs: tbs, filter: filter, lang: searchOptions.lang ?? "en", @@ -47,6 +49,9 @@ export async function searchHelper( } res = res.filter((r) => !isUrlBlocked(r.url)); + if (res.length > num_results) { + res = res.slice(0, num_results); + } if (res.length === 0) { return { success: true, error: "No search results found", returnCode: 200 }; From 115204e6b6c6437dc5b3409a9f7b384a8a4fe5b8 Mon Sep 17 00:00:00 2001 From: Simon H Date: Sat, 25 May 2024 11:57:49 -0400 Subject: [PATCH 2/4] Feat: Provide more details for 429 error msg - Added better error code for when rate limit exceeded including consumed/remaining points, reset date and retry-after seconds --- apps/api/src/controllers/auth.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/api/src/controllers/auth.ts b/apps/api/src/controllers/auth.ts index b0bfabb..daf0ea6 100644 --- a/apps/api/src/controllers/auth.ts +++ b/apps/api/src/controllers/auth.ts @@ -1,12 +1,12 @@ import { parseApi } from "../../src/lib/parseApi"; -import { getRateLimiter, } from "../../src/services/rate-limiter"; +import { getRateLimiter, } from "../../src/services/rate-limiter"; import { AuthResponse, RateLimiterMode } from "../../src/types"; import { supabase_service } from "../../src/services/supabase"; import { withAuth } from "../../src/lib/withAuth"; import { RateLimiterRedis } from "rate-limiter-flexible"; import { setTraceAttributes } from '@hyperdx/node-opentelemetry'; -export async function authenticateUser(req, res, mode?: RateLimiterMode) : Promise { +export async function authenticateUser(req, res, mode?: RateLimiterMode): Promise { return withAuth(supaAuthenticateUser)(req, res, mode); } function setTrace(team_id: string, api_key: string) { @@ -18,7 +18,7 @@ function setTrace(team_id: string, api_key: string) { } catch (error) { console.error('Error setting trace attributes:', error); } - + } export async function supaAuthenticateUser( req, @@ -97,7 +97,7 @@ export async function supaAuthenticateUser( team_id: team_id, plan: plan } - switch (mode) { + switch (mode) { case RateLimiterMode.Crawl: rateLimiter = getRateLimiter(RateLimiterMode.Crawl, token, subscriptionData.plan); break; @@ -126,9 +126,11 @@ export async function supaAuthenticateUser( await rateLimiter.consume(iptoken); } catch (rateLimiterRes) { console.error(rateLimiterRes); + const secs = Math.round(rateLimiterRes.msBeforeNext / 1000) || 1; + const retryDate = new Date(Date.now() + rateLimiterRes.msBeforeNext); return { success: false, - error: "Rate limit exceeded. Too many requests, try again in 1 minute.", + error: `Rate limit exceeded for mode ${rateLimiter.keyPrefix}. Consumed points: ${rateLimiterRes.consumedPoints}, Remaining points: ${rateLimiterRes.remainingPoints}. Please retry after ${secs}s, resets at ${retryDate}`, status: 429, }; } @@ -155,9 +157,9 @@ export async function supaAuthenticateUser( normalizedApi = parseApi(token); const { data, error } = await supabase_service - .from("api_keys") - .select("*") - .eq("key", normalizedApi); + .from("api_keys") + .select("*") + .eq("key", normalizedApi); if (error || !data || data.length === 0) { return { @@ -170,7 +172,7 @@ export async function supaAuthenticateUser( subscriptionData = data[0]; } - return { success: true, team_id: subscriptionData.team_id }; + return { success: true, team_id: subscriptionData.team_id }; } function getPlanByPriceId(price_id: string) { From 7948c6cee29d426485e9b1ab5e296c1d39c8eec7 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sun, 26 May 2024 18:03:37 -0700 Subject: [PATCH 3/4] Nick: fixed pip issues --- .../build/lib/firecrawl/firecrawl.py | 185 ++++++++++++++++-- .../dist/firecrawl-py-0.0.10.tar.gz | Bin 0 -> 5356 bytes .../python-sdk/dist/firecrawl-py-0.0.9.tar.gz | Bin 4340 -> 0 bytes .../dist/firecrawl_py-0.0.10-py3-none-any.whl | Bin 0 -> 4161 bytes .../dist/firecrawl_py-0.0.9-py3-none-any.whl | Bin 3144 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 254 -> 0 bytes .../__pycache__/firecrawl.cpython-311.pyc | Bin 6997 -> 0 bytes .../python-sdk/firecrawl_py.egg-info/PKG-INFO | 2 +- apps/python-sdk/setup.py | 2 +- 9 files changed, 166 insertions(+), 23 deletions(-) create mode 100644 apps/python-sdk/dist/firecrawl-py-0.0.10.tar.gz delete mode 100644 apps/python-sdk/dist/firecrawl-py-0.0.9.tar.gz create mode 100644 apps/python-sdk/dist/firecrawl_py-0.0.10-py3-none-any.whl delete mode 100644 apps/python-sdk/dist/firecrawl_py-0.0.9-py3-none-any.whl delete mode 100644 apps/python-sdk/firecrawl/__pycache__/__init__.cpython-311.pyc delete mode 100644 apps/python-sdk/firecrawl/__pycache__/firecrawl.cpython-311.pyc diff --git a/apps/python-sdk/build/lib/firecrawl/firecrawl.py b/apps/python-sdk/build/lib/firecrawl/firecrawl.py index 98cb8ed..6c0bc41 100644 --- a/apps/python-sdk/build/lib/firecrawl/firecrawl.py +++ b/apps/python-sdk/build/lib/firecrawl/firecrawl.py @@ -1,25 +1,57 @@ +""" +FirecrawlApp Module + +This module provides a class `FirecrawlApp` for interacting with the Firecrawl API. +It includes methods to scrape URLs, perform searches, initiate and monitor crawl jobs, +and check the status of these jobs. The module uses requests for HTTP communication +and handles retries for certain HTTP status codes. + +Classes: + - FirecrawlApp: Main class for interacting with the Firecrawl API. +""" + import os -from typing import Any, Dict, Optional -import requests import time +from typing import Any, Dict, Optional + +import requests + class FirecrawlApp: - def __init__(self, api_key=None, api_url='https://api.firecrawl.dev'): + """ + Initialize the FirecrawlApp instance. + + Args: + api_key (Optional[str]): API key for authenticating with the Firecrawl API. + api_url (Optional[str]): Base URL for the Firecrawl API. + """ + def __init__(self, api_key: Optional[str] = None, api_url: Optional[str] = None) -> None: self.api_key = api_key or os.getenv('FIRECRAWL_API_KEY') if self.api_key is None: raise ValueError('No API key provided') - self.api_url = api_url or os.getenv('FIRECRAWL_API_URL') - - - + self.api_url = api_url or os.getenv('FIRECRAWL_API_URL', 'https://api.firecrawl.dev') def scrape_url(self, url: str, params: Optional[Dict[str, Any]] = None) -> Any: + """ + Scrape the specified URL using the Firecrawl API. + + Args: + url (str): The URL to scrape. + params (Optional[Dict[str, Any]]): Additional parameters for the scrape request. + + Returns: + Any: The scraped data if the request is successful. + + Raises: + Exception: If the scrape request fails. + """ + headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {self.api_key}' } # Prepare the base scrape parameters with the URL scrape_params = {'url': url} - + # If there are additional params, process them if params: # Initialize extractorOptions if present @@ -32,7 +64,7 @@ class FirecrawlApp: extractor_options['mode'] = extractor_options.get('mode', 'llm-extraction') # Update the scrape_params with the processed extractorOptions scrape_params['extractorOptions'] = extractor_options - + # Include any other params directly at the top level of scrape_params for key, value in params.items(): if key != 'extractorOptions': @@ -41,11 +73,11 @@ class FirecrawlApp: response = requests.post( f'{self.api_url}/v0/scrape', headers=headers, - json=scrape_params + json=scrape_params, ) if response.status_code == 200: response = response.json() - if response['success']: + if response['success'] and 'data' in response: return response['data'] else: raise Exception(f'Failed to scrape URL. Error: {response["error"]}') @@ -54,8 +86,21 @@ class FirecrawlApp: raise Exception(f'Failed to scrape URL. Status code: {response.status_code}. Error: {error_message}') else: raise Exception(f'Failed to scrape URL. Status code: {response.status_code}') - + def search(self, query, params=None): + """ + Perform a search using the Firecrawl API. + + Args: + query (str): The search query. + params (Optional[Dict[str, Any]]): Additional parameters for the search request. + + Returns: + Any: The search results if the request is successful. + + Raises: + Exception: If the search request fails. + """ headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {self.api_key}' @@ -70,19 +115,36 @@ class FirecrawlApp: ) if response.status_code == 200: response = response.json() - if response['success'] == True: + + if response['success'] and 'data' in response: return response['data'] else: raise Exception(f'Failed to search. Error: {response["error"]}') - + elif response.status_code in [402, 409, 500]: error_message = response.json().get('error', 'Unknown error occurred') raise Exception(f'Failed to search. Status code: {response.status_code}. Error: {error_message}') else: raise Exception(f'Failed to search. Status code: {response.status_code}') - def crawl_url(self, url, params=None, wait_until_done=True, timeout=2): - headers = self._prepare_headers() + def crawl_url(self, url, params=None, wait_until_done=True, timeout=2, idempotency_key=None): + """ + Initiate a crawl job for the specified URL using the Firecrawl API. + + Args: + url (str): The URL to crawl. + params (Optional[Dict[str, Any]]): Additional parameters for the crawl request. + wait_until_done (bool): Whether to wait until the crawl job is completed. + timeout (int): Timeout between status checks when waiting for job completion. + idempotency_key (Optional[str]): A unique uuid key to ensure idempotency of requests. + + Returns: + Any: The crawl job ID or the crawl results if waiting until completion. + + Raises: + Exception: If the crawl job initiation or monitoring fails. + """ + headers = self._prepare_headers(idempotency_key) json_data = {'url': url} if params: json_data.update(params) @@ -97,6 +159,18 @@ class FirecrawlApp: self._handle_error(response, 'start crawl job') def check_crawl_status(self, job_id): + """ + Check the status of a crawl job using the Firecrawl API. + + Args: + job_id (str): The ID of the crawl job. + + Returns: + Any: The status of the crawl job. + + Raises: + Exception: If the status check request fails. + """ headers = self._prepare_headers() response = self._get_request(f'{self.api_url}/v0/crawl/status/{job_id}', headers) if response.status_code == 200: @@ -104,13 +178,45 @@ class FirecrawlApp: else: self._handle_error(response, 'check crawl status') - def _prepare_headers(self): + def _prepare_headers(self, idempotency_key=None): + """ + Prepare the headers for API requests. + + Args: + idempotency_key (Optional[str]): A unique key to ensure idempotency of requests. + + Returns: + Dict[str, str]: The headers including content type, authorization, and optionally idempotency key. + """ + if idempotency_key: + return { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.api_key}', + 'x-idempotency-key': idempotency_key + } + return { 'Content-Type': 'application/json', - 'Authorization': f'Bearer {self.api_key}' + 'Authorization': f'Bearer {self.api_key}', } def _post_request(self, url, data, headers, retries=3, backoff_factor=0.5): + """ + Make a POST request with retries. + + Args: + url (str): The URL to send the POST request to. + data (Dict[str, Any]): The JSON data to include in the POST request. + headers (Dict[str, str]): The headers to include in the POST request. + retries (int): Number of retries for the request. + backoff_factor (float): Backoff factor for retries. + + Returns: + requests.Response: The response from the POST request. + + Raises: + requests.RequestException: If the request fails after the specified retries. + """ for attempt in range(retries): response = requests.post(url, headers=headers, json=data) if response.status_code == 502: @@ -120,6 +226,21 @@ class FirecrawlApp: return response def _get_request(self, url, headers, retries=3, backoff_factor=0.5): + """ + Make a GET request with retries. + + Args: + url (str): The URL to send the GET request to. + headers (Dict[str, str]): The headers to include in the GET request. + retries (int): Number of retries for the request. + backoff_factor (float): Backoff factor for retries. + + Returns: + requests.Response: The response from the GET request. + + Raises: + requests.RequestException: If the request fails after the specified retries. + """ for attempt in range(retries): response = requests.get(url, headers=headers) if response.status_code == 502: @@ -129,7 +250,20 @@ class FirecrawlApp: return response def _monitor_job_status(self, job_id, headers, timeout): - import time + """ + Monitor the status of a crawl job until completion. + + Args: + job_id (str): The ID of the crawl job. + headers (Dict[str, str]): The headers to include in the status check requests. + timeout (int): Timeout between status checks. + + Returns: + Any: The crawl results if the job is completed successfully. + + Raises: + Exception: If the job fails or an error occurs during status checks. + """ while True: status_response = self._get_request(f'{self.api_url}/v0/crawl/status/{job_id}', headers) if status_response.status_code == 200: @@ -140,8 +274,7 @@ class FirecrawlApp: else: raise Exception('Crawl job completed but no data was returned') elif status_data['status'] in ['active', 'paused', 'pending', 'queued']: - if timeout < 2: - timeout = 2 + timeout=max(timeout,2) time.sleep(timeout) # Wait for the specified timeout before checking again else: raise Exception(f'Crawl job failed or was stopped. Status: {status_data["status"]}') @@ -149,6 +282,16 @@ class FirecrawlApp: self._handle_error(status_response, 'check crawl status') def _handle_error(self, response, action): + """ + Handle errors from API responses. + + Args: + response (requests.Response): The response object from the API request. + action (str): Description of the action that was being performed. + + Raises: + Exception: An exception with a message containing the status code and error details from the response. + """ if response.status_code in [402, 408, 409, 500]: error_message = response.json().get('error', 'Unknown error occurred') raise Exception(f'Failed to {action}. Status code: {response.status_code}. Error: {error_message}') diff --git a/apps/python-sdk/dist/firecrawl-py-0.0.10.tar.gz b/apps/python-sdk/dist/firecrawl-py-0.0.10.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..59e42d15d1c8be849312b82375296c14525340d6 GIT binary patch literal 5356 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 E4^1)K*Z=?k literal 0 HcmV?d00001 diff --git a/apps/python-sdk/dist/firecrawl-py-0.0.9.tar.gz b/apps/python-sdk/dist/firecrawl-py-0.0.9.tar.gz deleted file mode 100644 index 55e0eed7b11f182f42a69f4f864156a6e79ba381..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4340 zcmVA@4POsa0M7})QK}khIV;KI? z!z{h0WSQ_K8+8x5!(Ol7>m4|Q!P8#}g_YOmvTobTJ;#QqNk z1NC>P{&sp*oA2&}_6-k){R60nFVKt(c4_|spN2jaQ@W%vt!W3%X0yNE`a1^y4gPQ9 zf44I{F!=v4@&EnLKkdDG`{Lcb+W&BWe=Gj?JNs++KR6g1JR+T4+W!N^|2Hh59!==p zFDw>33=T=p=~{2;k{yz=ZdmzssU+5Mx&%S34$1pfvIqlm{NvAL7RKa7)`vWM|H^t9 zF4!+*0U7o#fRjLzUWY{>-N}^a{rLP zN}c^{S-Nbj_)qTtd&5ot-#7k$C;q=z`2Rr<{u>@R9b-`s+5dkydiLX+BWLOD#{Yw{ z4~OXgy9eO-hx>i7|6#Xp{QpkMlaj|ST)Aa^T(H_JgbPB6&nGc>f08U{La6VD7lN$9 zlq4ZxRB(Sq1ayoTp@Byfi3>InJYk|G>%ugN(ggZ5g3$-z0Z@hbVF~nNa78N_Or(O5 z3y2qpPlI_1VFX!*9`glhyng*gG#&CPAyFKjbB`fcf-fUqjmd)~IGaxwPc`r!0rEl47ziN_m*gbx<9O`+w2?(pmFlL|bI37M zBF(x}fy7vF&f_qEh$$iGH0BsKkpLn*kBncu`f&98!?Vw?UFhTfeDt3&0VzdP2*MND zAHKxU7Q}|Di`2*Y{GUb9m`uTH1W3(#$zo=!5x-Em65)(2!VBz&LczWuDJ7UlO3soK z%qK!%0mC>Unbup96Zbm0 zQp^G_Us?o;jZyQv2=!0chq_W;2m^ohhVtNf2)Z03aPecDGI{;Vy3!-XOr(CI`O61y zlwr6^GM#ioc_`HGH<*^de6$s+WmT_*Glj1pTw^L0%^6OCDt z#(`?0g{rVXw)OZF=MEs_l=Wpzf?CmVH*u?#!CB%YkG>=s3Wy|5r@%=zy3|NL_s^(X z05zb{s(ly~cCLX=U)n+^SRo~ce1Fyk*S7zSwI zphW<_r!kkxK6(rzkO2%C#+-@AU`_Swnd?BnE`+lJekVMj&`T;qD>Ea#JYigGOU1e> zCefJIC+J9@Y%Ugan&-o=RPJ2u3|)1&QOlel5Te5u5TEI9Jc( znw}yocOp*!|Ky_p} z7sNqOqKpNPXa<$sIYuqGVFihdNdw|W!KPv0i6(Z6QFEDBVeJL=MICQitvTSF$7#ZV z!#pBHW?ucM#Gm2)k_PE*=OHptjb&q3sj6z8Yik9*>*9locUzASWDm9t)~0?6pbE7o z_QVh8?eFZ<5@zwBxPICqPl(Sbgvx9gB`Wa7NLA`)(Z}aYjtJF-En90lx_#?NrA5}X zeuOk(b(Knt1{zU70XFReG^9phH-V(0AjQ&ZR>NS*axE!qs#Lq|!i;j*sNj}Epfigg zGHI#Uc*>-()F2ZuC&~e=9gTvhFQ`2#f;^TgqkynR?PwWl3Siu>b5(>?YJO92HcjIg zVQ`DKSM(UZyWE4-EHCbf4b`ta`_$4T^kQ$Y*-+hcr$CP47$o=-I6}{$U=KH;3#@@j zFuX1_w}1?j)#ipy%?D*HaM(BmuL|Z~EQFBjhD#$y3#Tw)LA*3p&M0Q!sYG(^;wj{y zMIeLLB&jW#z|Ec7RiYupe3B+|GQ~Ia9eQ(ByXU4cdT$>Tjr|RIMM#=)m=?yDTnOL~ zHEn@LEYdmHZQ+k#O9eY*CLJ8u5?DH@Nf@yJ=Og=KSCLMZxZxvFDm7nD88lt;^b=2B zrW2@*LU6&M49Q_OulbY(f?a>F07u#ruSIQ%*xh1B)z1mz zdD~alMH?D79dqB#od28q|KINX|DZQKFz5dd^!|Td)xJmjzgzy-5RU;&{+BubHTb{7 z^WT22yZ>~sZ_a-W{ww@$rZvM=+$ItTr3Z`c7W=)uRKIse_s`r|Q$ zbNvaNu@bZYGyeat@BeZC*Bv}H{@?h2<^PKeldlQ@Zpi-{40^@>4|Bo5`nw0l|L>$| ze;*2!ij%Bl?uV{@7OYz2M?Otj+}LFNWswra# zQj)n~_uuz*u0uD><0#7ja zj=ayZi)7YHg&H*4OnAY}%MDJev)6qWRL~@e8yl@pHmRPrh^=yE%4U15bn2*p#BSBj z$?|eGhI&Gd0+E6Ss5~8g0*o0)Gn)D`J0uA=l6P>rL72ecXb3#<3wyV2F%dabE%R#S z<@MIasCC?biab24EOO*B1ITSe>oM~-6mx@Ig~G|E0@kuzp|W8WGLJwWLxwWRM04jM za--O&QrIMnh|kWMkEh2KHYybHyb_S3Ma~g)Q8l%}$V{^ZL5=bg4_z+sk-z)QP)01zjYg5$Sb0Yu>NSyzYa8 ztQlE}=yztd}x{mxVT=ld2JbUJG@krZ8hroSl}4eZzm?fX-3 z7K9g-yn?tAG2CT(Dieetucb+rr*Kuspu%T${-QR@xtH-v;}n66O7>Ghj7<5<6MAHn z^2Z8KWRj=OH@&nayFftxy}Bt#$oo2}1nowaT73_2Acc5mILJBrJ)r<8VzSlFAVJFN zmT*wa#ns-})%7K}soS)S0jp0IYsax|Pl2x7lQ#XjiLiD&KCyJlABr)g;xNmSKR|UM>z* z$3aUFEw+AdJ0$9D5By+CHuKjfg9O$af5;R-9;W^Mcp=23zgpK1jFB= zOdD$slD>J#+iLjsrJk+*mFvC+n(H`xR~f3LQYAw*bXklW(m-M*;blv@bAMgpuxxlG zY^j)73pvHdNk;t^nb7H3IGedMxmg%>oIzN)HFW3ZHD0mo&gJ;) z-dTBM3ifY*u%%0JmKeeQ8;IJ4DOwu~x^q5!_0=X|KIbp(gCC zjwK(KvNe>+;M30Y^S9g)!24n{^h8B0k*RN8c9Z2HYl7G7)ni+-k*#glwWVdMjQ3qWm!C)~9{98FvV*)tM!PI+M6Ufi33Z6WKK3UOg=74~lLf9(VQgZZ2Mznv8G{?~_H zjQ=XF{W<(zw!UZ{~P~r{QtH7zr4-5=ly@DyYBz{`~7|6 z|94UH=lG2&82@klzw!UZ{~P~*&-YMo_W$em|NDcX`Te)uuD`K)`~ObI?Ej7bkG}Gi|9A5LgI>x1@1y<8+W^M@?_&M&t-o@`lQ8r} z_8JLU(~>_j=SFmTCVvZ3r3k9mmErG)kL!0H zkL=QO0F^7Qd>UY6uRjf-8HWI2n@YH;oVpa1~M0k@(6 diff --git a/apps/python-sdk/dist/firecrawl_py-0.0.10-py3-none-any.whl b/apps/python-sdk/dist/firecrawl_py-0.0.10-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..36676fe13fdd4260d9ca6decb804ef29aae495a8 GIT binary patch literal 4161 zcmai%c{G&$`^SeY*=66xR*`LHG?rvvhGFbmwn!N3SjIY$ElbJ1MfOP%iiXI8k|73T zUz438*~t?6>H9t3o}Ny>^W68j&V4?A+^=(8_jSGRbG;1_XUJFp0Ki!QcWIJE64qy$ z|MYa>G+0jq>56u8K-&j;fbHyDJzf3m>_FZagji>TB}7R7hCg0s&v8qD$Fze1T$K>_Zy~ZwT+53N&+6Pp?VqaN%)+gKg!}$ z0s;akyawXhSev@&yHXn^Qm;DPdE2J2=qM1Dj1%=7jPg;_&Nb>Gc*zjF9L-HH5XsA3 z#0QRIK0Lmu-HxJ9ejk+sU2vB!nA zm(xaj+qBpeqP9&Y6=}z>?{jQwv)vD<#1HIOrxVCorG?vQ!+eBB;|b&+`bUO%TCaUk zmY$D9OoqLFb>*VZ3yUEPmq`j+{eoPqa<+|FS^Cq*lI3K2zA!ATjhsV@mo~9aHr{}O zOsS#uYkyQDOmwkHZ}R1tU?`m3$%x8$<6{n--2+k{M}Dr0iLkqi50MkB9JGk<_{M}P z%r5e*0Mb(EkZZ|41ykd{3lB_K9?JWgwT!`itF9z*j>RDu%DGYEp)T2)1>>nv2|EZ- zS@|p(N+9SA@rg8q7`?M~K6650MRaBR=pLFAlDf=AkpQF0Nx9T=DGr-l?IGjlm&X_j z&Q1-4^=Kwod|wq9KW;c0c$lc$o^Wh3Pi6RQW}aKfB9xoLOTQ;EsfS+qgefAX|4rg| zAT8Y;zmHqtL%tVhO0Xgv$6`dc02?zHE5$nXyiFeVUfYnj27`VAm~W6;fh!&kVaG0{ zH7W9Q_Hwl+G6$5`KhiQA5Y$>VS%z#d7ek&cMOshcOB>Vc_~Dg2S}oBdoyhhL-{&f- zNI$}yUnWjtEr=+IRtWZlPrhnRiWe{h2vSA>#yLK4%8O;JnK%3UwFl{^nogRQzfEJ6 zjVOJq>zw;|n3LH*BalUX`>{@(xBwL^3!cQ%m6wCAG11}OK}C;8A_pTdrhQK`t<0c< z(EW%pyI*SFp?&dVdu~4&Dm#@j*45h-V6%90@1`a0h8n3=X0~Q$lR;z0os@l=Mq)9DDN`?xP(B~lv|WN5y^W zChxLr2dfy>2Momvc(<{X;0rYQ;V=-h2C zL4K7mvdaXHA^m0{zph%D#KFvyI0^J^j=E6_lu z26{XDOV)eAw8>VXnL!yG`>n-CpANnSGIi7O0V0`L?D-2_+uu38g)MOb6~_F7Uj{b3 zDM|lwqk>Il`1@qeU4lt_?bp|X{!%&#W@6m-Vu&Xu=&Co)#qcp_&K$B`Co~0R!3OWaeSqGH(e^eWxy_ zO@!!4Ee*yT*9WV$-CD`75|}qk-iHQ8C+fU@UUhKd>_VM#{ercF$I1d5 z$(e&NCwz7#3|cd9mDz15;r8fQEZp*{kNXW7u4BopBOst=XP&(oBky-&0;rZ*2_x;y z>8d5Myjc=l8pw#zSaT7@a?vwcrZ-0~!_4qzJUXl|vm)1v*lp9v`PCa+Lqcgg47wdF z^FF;SJV?+#cFWpq1XS)w?t5;p?ca|Yg{6?-lYE7O9sABcV61Pw8S@@RKxiVEiHya?e@g%DL`YCTdl7Y-fixlp@WhCvSB-ep_Xjh4!L4$=27FY%3#0w|78lGpQ=RC2diVm4m?~6g5e=#mQ zs1#Sm0<~)IePM&TYueY&tk7heQY;LI0 z>_g~AN_TkB5&GsAk?60ye!66K*}%MyPbbPz+^(Glos&8>Ko3Q)Qo{-C)d}CRcf%Hn z8!2}v$G;iXVni^Rq62Iw$4a&8erv^mR*ieZPs#c6XVZAU>0VAnc^74LXC?|3VwI_V zT0V1qp4^S)-SG$xPnF8y#VQoHFZ0mz4+9I*Q5XuiixZm*|gk zNi(>YgaK3BxxzrmY8N5Oh{5B{a_JNhWylb8YOp(h%j2HvrCnqU);mXHoIJ|2?&(1m zi6DB=fVdexxVU`TV@}a8&3*}qm*L5Yo&IYuMotwGzf`Y6PzCkd6arD}9Y zs8}WQZ6Idn_9_v5hr*dCVD=EnXsgJ%p1tEDf(z;d9}g^;o>y~@^k)#!ylodw(3)0L zc}|6S{p5g-M!IbyMZHNeA^GhT5L6mL8bf$5=TRr#^j>+XPIXK&0rSp+Gobd%MJk4l zt_KN+c!`H)ns-3)St}^?#O_$<>Zgv+VLZxV#uEw+a>qj~H9;o@j)e~WfqCIUA)AZS zdwG8OkJ4}RZt|3lTzD8NypjU#JbZMR#vm+1F!AjPoSQKysPT~dv_$*0ftk;}u|T3u zNta;sP?wGNwsRao+qi34QwuXaHANhZKgYA>pC}~y`px89YUO>abg@tOoy8HdiZ_?h z8@e6WE#ZNjOU%QpI#k{H6`1&VuwmA6ecQV?T*^dUJlk^iV_CkiPHtqLs<*e}<;sL7 z?swle8cHkz7p?A0saR`Uuh8+lpxn1=c@|<+R4h%&wom7FzLh03da`nMF2imXY1&x0 zS9GFub!nwfd>Am!\{qFX_NtQ*{P*kukOj=W}smOKhg-@Q=H;3JkJgD>y%JC&N zP#n%t8|qZhtojc9KJjo?$;uY|-gj(0YVASq5$I*>R;ERhE{*~pmYGg{@s;T8=iJIt zzV%F6>Zqov8)L5YaVrMmY^bCgbj&s%%-+kG%}CGtWMsOjl>_v_jJ~1^NE_$}kK;o~ zPnRrtw|Ypg_IG2T;6)--FqchbM~YcyCE89-%&_nwL?B{>!>I~6**w#Lshd&7V0N{X zS`U+}gW+zi;@7;c$7{SC@^VH!2D)t8{k^x^oNcfmrpd#xr~B&EX$bwgx7vAQBp@IN zND=~aboKRrbf%Rafsv4@Ms(J~k^pOx2jXlM-H!9YC;~5uPU1($Ab0=r`T#6-X zFUCwvkTX4ig1w#;-vw$e7;LG^m(N_H|B@KPvh&_#WfQuIagy)V5TfTSXLsnUq%M`}Y^p)ioQN11Btb0?^WvA_rJ?hx;5XiR@K5pmwixmaMr9(vBLKI}z^cCf8#y=ug* zl`EV>X{6;7qlBYb%BBYkl(tKZtotk?t<7WX|E#j;vE-V~X>~v1{J&Ot4Gx3p()7ah zi2Za33DKVJ-fCmW5OfA@KtxFBh!em#W4I1M9CQy%`P8Tn(g&^WgZ3{_clLt&cg08O zE=lMR3A#ArOCq9ri`3e>b;g%UrACNdy@+AG?(&5>(qF8@j~3`c@6gkpCUxo_0Al}| zoS&DsorhC^lLyEz$nU2+nf)C)VC(xDOacH*p2iQG^V5a=v9HE3bpvCK=sx+tNC+Ti zw&@LdvUf(&89{gx3=$VWAfXdV0SMY|jMm++&6$u@T$i)!;ljBOSohxZl}btf8kVzUCjzM%wg1{pc~dcZ7)$kSN16kS zC~*S|ciqQ3f(BD~_oSWqBxy>`@@hEWwby`2^vDkjVpsIdS8l7 zNCO_vpX%!IG=I6m9ND+Q=^S$FKvCj;66#X}YhUS+v{x}E!)A#Sp-}Uo1bMs%B z|54NLz~5*7Z(!o76n>WX$0YxufZxHt&;9?vH6-)@3jVJO3=!lMKi;A^y+=>gV@m(y F^nbsdBE|p! literal 0 HcmV?d00001 diff --git a/apps/python-sdk/dist/firecrawl_py-0.0.9-py3-none-any.whl b/apps/python-sdk/dist/firecrawl_py-0.0.9-py3-none-any.whl deleted file mode 100644 index 83cb5b77411b29a142592767d8f15946d19e22ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3144 zcmai$c{G%L8^^~wB2tzTQ<@~pm@&2t5oO9?msR|MNN5_4{4lbA2(UJiOun06+jBeK*E7 zCfj2`mh&gakpxHQT|7wyPe&>lhR3_Oxp?F8Pd6M`60E{#MAo&aOJDkJpwY^ATYl(izz}G+6tFW_x zKvcQUx5R*$29wW9Yji~I6W~3bP0%XMiAzj)xj-KPp;{B4mZM(T1R4{g9w$doSs+D8 zYeh}P-afM=p=^rMNt~$TiRGvpDeF)yC?WZ}B&mK;Wf`?dcP%Qv5ae1ic;<{tXCjyt zHkwbgS+%#Gb=&TzroSHr7R*YJ!ydlX6lM*s8a>prlT<%je0t1M=T$ZK5u2^5nxX00 zEQNm&ClS8QWS1@%u|E==%X_4GCvNGSRJT(^vB+oqYz-}A1_Y9)?^!=~q7VR|ZWE2l z1W6087|V+vq?bJ&17efZvSB9Nr2<8FiB;lKtixG_M($7T!YT7*Q=`3J2kXAL)ME#! zO&&F&LhpPpzfhBxNFZnVvO$O@VI9p|4z-<~2AY4Vfn(@R!B;K3I@O>O0eGMFoHY_x z^W?K)ijp~X3(5NWmBg%-*~$Wl^P8KsHq$i4CS7$N$>$T4bQH9;4d{brTTg$U-G_*m zIXq~@LLi&~_kDV+`DBBGZH}@!s0^pjBIHy57<~+bH?eWxs-FdGXe*KdwrValvn`hi zfV(-g?Xz`!(GVj9q&y?U@@FaALf?5b6h9#5Lv!B(NRp@`Kz(PnuRXTQ=O9no>hcH@aC+1zW11oqo*9Yi42-TB9IG?9t>papEp16}kF&5H9ZND} z6DQ>jZtRC%qiG2|4}N$ptj7_+kDfD@A|lgXN#)pZjb8b4j60p!@>XxtTG6{DQP%N?ia63lK444tU-#0yMH4uxFd~fY&6Q_oD zRn!GF2ENhNs@PH9>-B=_SW}-#aC-Gs|LHAE?H#9{76Yf)&a+}X2w6&+NV9j)Z3B@o?xyCg1q5O;*^WX?~E3a0&sptlcvdoPvXE%k*_skD>T^ETdCj%P6;>wxEU> zIqo>%(W~t-VsGW!gK)QB;mw~RGY*^MPMydS*8>^@TI*Ny8S+t#HIif=)#RKnjziOLBi#t9n0APW|-wWF-VdiF_}0sGJ1@M-h>+iE=lFUoONh*w?b!YRB9_Rq^Rc132S zERZQcT_v6l*QKz=+Y)`#6%#?0-Nvp6M6sr#N#g}f)AcfkF3r;yE3<`^l?$IQXdXZ`{jF8ySSWY?9C!ANuGeTMV6<+4^D28i^d~ zA!yQO|7@QgawJ-VcqZUl*l9N#h(&vb4WP3eZ_pfd?k3NjuBtb zW$|+kGeW;MnUxEl!*QDXCh+f?v@u2^&4gIS=#Fk-Qx&Do=d3cU>Pz)OPm2yy6%$A+ ztQ2c((h7lIh5_?WcByu$S9Ga&j|#T2VBMb}eZmSVCLOJ2rC0?;CG>kilvyQK;jTts zM?1^37yX<$I?Vl3l=yZ5X6m6LTRAlea-{UT+Po?5crwYCM233%d4CTpLz|h+6FiO( zE&$*)NBe)r#3GMbV2#2%Yy3l00pUY$UAOEK#at3;p=>vDVZ1o7?E=F{)I?FiW&p)* zb#wBsP3XBM^N4(JY*&xaEPRYDy`rRk@|1mu<{Mr`=(gxgD%C;z-E8tmXZ!Q(UAClZ z2f3nZSB%gf_c7;J8~yJcc|G`OT0nl-u`I0tMHM}h{3Yc?g`MFUmC;yTQpwP`8=M0UVj7~Yd)T!AS<8Gj|RS5Ok_DXJH ztw>cv#oq0r$RR|AcHZP$l80O&4A&X|L4G?E{m95*!i!3txj2_dV&vmYpE3F(_}7=K zDW`F9!BzqE*?F!@mt6AAQ7Fpp)WvGwNaAdjnpGy0c~b5SXi19nZdp)ZfhMc*Nmc`p zvcb7PE^cw2{~a(nC;s=76a4M-JrVvKH-AR_TcG?00086!TmFLh4&%hjpBevVc_-}Ru{0ESR8Z`g_ diff --git a/apps/python-sdk/firecrawl/__pycache__/__init__.cpython-311.pyc b/apps/python-sdk/firecrawl/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 605b3df712dacc3f938e664f38e99d9bfcddd4f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmZ3^%ge<81Z`7g(^P=;V-N=hn4pZ$LO{lJh7^Vr#vF!R#wbQch7_h?22JLdj6exZ z##=mYnMJ9|MTzA(js*pNnoPGi(_jKc%s?e889sxI`W31lTAW%`te=;eoS&0eoSc}O zSd^&mlAm0fo0?ZrtnZ##;!&EU57(@pSWr-`Ur2KczG$)vkyGXbZ?q#j-%+12ZEd;|&Jo3)oN*8&CxRaAQTu diff --git a/apps/python-sdk/firecrawl/__pycache__/firecrawl.cpython-311.pyc b/apps/python-sdk/firecrawl/__pycache__/firecrawl.cpython-311.pyc deleted file mode 100644 index 7c98fa33cdb39232d3c4b0ad07ee506b91bb20d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6997 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 Date: Sun, 26 May 2024 18:07:21 -0700 Subject: [PATCH 4/4] Update auth.ts --- apps/api/src/controllers/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/controllers/auth.ts b/apps/api/src/controllers/auth.ts index daf0ea6..a335f8c 100644 --- a/apps/api/src/controllers/auth.ts +++ b/apps/api/src/controllers/auth.ts @@ -130,7 +130,7 @@ export async function supaAuthenticateUser( const retryDate = new Date(Date.now() + rateLimiterRes.msBeforeNext); return { success: false, - error: `Rate limit exceeded for mode ${rateLimiter.keyPrefix}. Consumed points: ${rateLimiterRes.consumedPoints}, Remaining points: ${rateLimiterRes.remainingPoints}. Please retry after ${secs}s, resets at ${retryDate}`, + error: `Rate limit exceeded. Consumed points: ${rateLimiterRes.consumedPoints}, Remaining points: ${rateLimiterRes.remainingPoints}. Please retry after ${secs}s, resets at ${retryDate}`, status: 429, }; }