Skip to main content

asimov_apify_module/
api.rs

1// This is free and unencumbered software released into the public domain.
2
3use core::error::Error;
4
5use asimov_module::prelude::{Box, format, Result, String};
6pub use asimov_module::secrecy::{ExposeSecret, SecretString};
7use serde::Serialize;
8
9pub use google::*;
10pub use instagram::*;
11pub use linkedin::*;
12pub use twitter::*;
13
14mod google;
15mod twitter;
16mod linkedin;
17mod instagram;
18
19const GOOGLE_SEARCH_ACTOR: &str = "apify~google-search-scraper";
20const X_FOLLOWS_ACTOR: &str = "C2Wk3I6xAqC4Xi63f";
21const LINKEDIN_PROFILE_ACTOR: &str = "VhxlqQXRwhW8H5hNV";
22const INSTAGRAM_PROFILE_ACTOR: &str = "dSCLg0C3YEZ83HzYX";
23
24/// See: https://docs.apify.com/api
25#[derive(Debug)]
26pub struct Apify {
27    pub(crate) api_key: SecretString,
28}
29
30impl Apify {
31    /// Creates a new Apify client with the provided API key
32    /// Returns an error if the API key is empty
33    pub fn new(api_key: SecretString) -> Result<Self, Box<dyn Error>> {
34        if api_key.expose_secret().is_empty() {
35            return Err("Invalid API key".into());
36        }
37        Ok(Self { api_key })
38    }
39
40    /// Makes a POST request to the Apify API
41    fn make_request<T: Serialize>(
42        &self,
43        actor: &str,
44        request: &T,
45    ) -> Result<String, Box<dyn Error>> {
46        // See: https://docs.apify.com/academy/api/run-actor-and-retrieve-data-via-api
47        // See: https://docs.apify.com/api/v2/act-run-sync-post
48        let url = format!("https://api.apify.com/v2/acts/{}/run-sync-get-dataset-items", actor);
49        let mut response = ureq::post(&url)
50            .header(
51                "Authorization",
52                format!("Bearer {}", self.api_key.expose_secret()),
53            )
54            .send_json(request)?;
55        let response_body = response.body_mut().read_to_string()?;
56        Ok(response_body)
57    }
58
59    /// Performs a Google search scrape
60    /// See: https://apify.com/apify/google-search-scraper
61    pub fn google_search(&self, request: &GoogleSearchRequest) -> Result<String, Box<dyn Error>> {
62        self.make_request(GOOGLE_SEARCH_ACTOR, request)
63    }
64
65    /// Performs an X/Twitter followers/followings scrape
66    /// See: https://apify.com/kaitoeasyapi/premium-x-follower-scraper-following-data
67    pub fn x_follows(
68        &self,
69        request: &TwitterFollowingScrapeRequest,
70    ) -> Result<String, Box<dyn Error>> {
71        self.make_request(X_FOLLOWS_ACTOR, request)
72    }
73
74    /// Performs a LinkedIn profile scrape
75    /// See: https://apify.com/apimaestro/linkedin-profile-detail
76    pub fn linkedin_profile(
77        &self,
78        request: &LinkedInProfileScrapeRequest,
79    ) -> Result<String, Box<dyn Error>> {
80        self.make_request(LINKEDIN_PROFILE_ACTOR, request)
81    }
82
83    /// Performs an Instagram profile scrape
84    /// See: https://apify.com/apify/instagram-profile-scraper
85    pub fn instagram_profile(
86        &self,
87        request: &InstagramProfileScrapeRequest,
88    ) -> Result<String, Box<dyn Error>> {
89        self.make_request(INSTAGRAM_PROFILE_ACTOR, request)
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_invalid_api_key() {
99        let result = Apify::new(SecretString::from(""));
100        assert!(result.is_err());
101    }
102}