def reply_invitation(self, invitation_entity_urn, invitation_shared_secret, action="accept"): """Respond to a connection invitation. By default, accept the invitation. :param invitation_entity_urn: URN ID of the invitation :type invitation_entity_urn: int :param invitation_shared_secret: Shared secret of invitation :type invitation_shared_secret: str :param action: "accept" or "reject". Defaults to "accept" :type action: str, optional :return: Success state. True if successful :rtype: boolean """ invitation_id = get_id_from_urn(invitation_entity_urn) params = {"action": action} payload = json.dumps({ "invitationId": invitation_id, "invitationSharedSecret": invitation_shared_secret, "isGenericInvitation": False, }) res = self._post( f"{self.client.API_BASE_URL}/relationships/invitations/{invitation_id}", params=params, data=payload, ) return res.status_code == 200
def search_companies(self, keywords=None, **kwargs): """Perform a LinkedIn search for companies. :param keywords: A list of search keywords (str) :type keywords: list, optional :return: List of companies :rtype: list """ filters = ["resultType->COMPANIES"] params = { "filters": "List({})".format(",".join(filters)), "queryContext": "List(spellCorrectionEnabled->true)", } if keywords: params["keywords"] = keywords data = self.search(params, **kwargs) results = [] for item in data: if item.get("type") != "COMPANY": continue results.append({ "urn": item.get("targetUrn"), "urn_id": get_id_from_urn(item.get("targetUrn")), "name": item.get("title", {}).get("text"), "headline": item.get("headline", {}).get("text"), "subline": item.get("subline", {}).get("text"), }) return results
def get_conversation_details(self, profile_urn_id): """Fetch conversation (message thread) details for a given LinkedIn profile. :param profile_urn_id: LinkedIn URN ID for a profile :type profile_urn_id: str :return: Conversation data :rtype: dict """ # passing `params` doesn't work properly, think it's to do with List(). # Might be a bug in `requests`? res = self._fetch(f"/messaging/conversations?\ keyVersion=LEGACY_INBOX&q=participants&recipients=List({profile_urn_id})" ) data = res.json() item = data["elements"][0] item["id"] = get_id_from_urn(item["entityUrn"]) return item
def view_profile( self, target_profile_public_id, target_profile_member_urn_id=None, network_distance=None, ): """View a profile, notifying the user that you "viewed" their profile. Provide [target_profile_member_urn_id] and [network_distance] to save 2 network requests and speed up the execution of this function. :param target_profile_public_id: public ID of a LinkedIn profile :type target_profile_public_id: str :param network_distance: How many degrees of separation exist e.g. 2 :type network_distance: int, optional :param target_profile_member_urn_id: member URN id for target profile :type target_profile_member_urn_id: str, optional :return: Error state. True if error occurred :rtype: boolean """ me_profile = self.get_user_profile() if not target_profile_member_urn_id: profile = self.get_profile(public_id=target_profile_public_id) target_profile_member_urn_id = int( get_id_from_urn(profile["member_urn"])) if not network_distance: profile_network_info = self.get_profile_network_info( public_profile_id=target_profile_public_id) network_distance = int(profile_network_info["distance"].get( "value", "DISTANCE_2").split("_")[1]) viewer_privacy_setting = "F" me_member_id = me_profile["plainId"] client_application_instance = self.client.metadata[ "clientApplicationInstance"] eventBody = { "viewerPrivacySetting": viewer_privacy_setting, "networkDistance": network_distance, "vieweeMemberUrn": f"urn:li:member:{target_profile_member_urn_id}", "profileTrackingId": self.client.metadata["clientPageInstanceId"], "entityView": { "viewType": "profile-view", "viewerId": me_member_id, "targetId": target_profile_member_urn_id, }, "header": { "pageInstance": { "pageUrn": "urn:li:page:d_flagship3_profile_view_base", "trackingId": self.client.metadata["clientPageInstanceId"], }, "time": int(time()), "version": client_application_instance["version"], "clientApplicationInstance": client_application_instance, }, "requestHeader": { "interfaceLocale": "en_US", "pageKey": "d_flagship3_profile_view_base", "path": f"/in/{target_profile_member_urn_id}/", "referer": "https://www.linkedin.com/feed/", }, } return self.track( eventBody, { "appId": "com.linkedin.flagship3.d_web", "eventName": "ProfileViewEvent", "topicName": "ProfileViewEvent", }, )
def get_profile(self, public_id=None, urn_id=None): """Fetch data for a given LinkedIn profile. :param public_id: LinkedIn public ID for a profile :type public_id: str, optional :param urn_id: LinkedIn URN ID for a profile :type urn_id: str, optional :return: Profile data :rtype: dict """ # NOTE this still works for now, but will probably eventually have to be converted to # https://www.linkedin.com/voyager/api/identity/profiles/ACoAAAKT9JQBsH7LwKaE9Myay9WcX8OVGuDq9Uw res = self._fetch( f"/identity/profiles/{public_id or urn_id}/profileView") data = res.json() if data and "status" in data and data["status"] != 200: self.logger.info("request failed: {}".format(data["message"])) return {} # massage [profile] data profile = data["profile"] if "miniProfile" in profile: if "picture" in profile["miniProfile"]: profile["displayPictureUrl"] = profile["miniProfile"][ "picture"]["com.linkedin.common.VectorImage"]["rootUrl"] images_data = profile["miniProfile"]["picture"][ "com.linkedin.common.VectorImage"]["artifacts"] for img in images_data: w, h, url_segment = itemgetter( "width", "height", "fileIdentifyingUrlPathSegment")(img) profile[f"img_{w}_{h}"] = url_segment profile["profile_id"] = get_id_from_urn( profile["miniProfile"]["entityUrn"]) profile["profile_urn"] = profile["miniProfile"]["entityUrn"] profile["member_urn"] = profile["miniProfile"]["objectUrn"] del profile["miniProfile"] del profile["defaultLocale"] del profile["supportedLocales"] del profile["versionTag"] del profile["showEducationOnProfileTopCard"] # massage [experience] data experience = data["positionView"]["elements"] for item in experience: if "company" in item and "miniCompany" in item["company"]: if "logo" in item["company"]["miniCompany"]: logo = item["company"]["miniCompany"]["logo"].get( "com.linkedin.common.VectorImage") if logo: item["companyLogoUrl"] = logo["rootUrl"] del item["company"]["miniCompany"] profile["experience"] = experience # massage [education] data education = data["educationView"]["elements"] for item in education: if "school" in item: if "logo" in item["school"]: item["school"]["logoUrl"] = item["school"]["logo"][ "com.linkedin.common.VectorImage"]["rootUrl"] del item["school"]["logo"] profile["education"] = education # massage [languages] data languages = data["languageView"]["elements"] for item in languages: del item["entityUrn"] profile["languages"] = languages # massage [publications] data publications = data["publicationView"]["elements"] for item in publications: del item["entityUrn"] for author in item.get("authors", []): del author["entityUrn"] profile["publications"] = publications # massage [certifications] data certifications = data["certificationView"]["elements"] for item in certifications: del item["entityUrn"] profile["certifications"] = certifications # massage [volunteer] data volunteer = data["volunteerExperienceView"]["elements"] for item in volunteer: del item["entityUrn"] profile["volunteer"] = volunteer # massage [honors] data honors = data["honorView"]["elements"] for item in honors: del item["entityUrn"] profile["honors"] = honors return profile
def search_people( self, keywords=None, connection_of=None, network_depths=None, current_company=None, past_companies=None, nonprofit_interests=None, profile_languages=None, regions=None, industries=None, schools=None, contact_interests=None, service_categories=None, include_private_profiles=False, # profiles without a public id, "Linkedin Member" # Keywords filter keyword_first_name=None, keyword_last_name=None, keyword_title=None, # `keyword_title` and `title` are the same. We kept `title` for backward compatibility. Please only use one of them. keyword_company=None, keyword_school=None, network_depth=None, # DEPRECATED - use network_depths title=None, # DEPRECATED - use keyword_title **kwargs, ): """Perform a LinkedIn search for people. :param keywords: Keywords to search on :type keywords: str, optional :param current_company: A list of company URN IDs (str) :type current_company: list, optional :param past_companies: A list of company URN IDs (str) :type past_companies: list, optional :param regions: A list of geo URN IDs (str) :type regions: list, optional :param industries: A list of industry URN IDs (str) :type industries: list, optional :param schools: A list of school URN IDs (str) :type schools: list, optional :param profile_languages: A list of 2-letter language codes (str) :type profile_languages: list, optional :param contact_interests: A list containing one or both of "proBono" and "boardMember" :type contact_interests: list, optional :param service_categories: A list of service category URN IDs (str) :type service_categories: list, optional :param network_depth: Deprecated, use `network_depths`. One of "F", "S" and "O" (first, second and third+ respectively) :type network_depth: str, optional :param network_depths: A list containing one or many of "F", "S" and "O" (first, second and third+ respectively) :type network_depths: list, optional :param include_private_profiles: Include private profiles in search results. If False, only public profiles are included. Defaults to False :type include_private_profiles: boolean, optional :param keyword_first_name: First name :type keyword_first_name: str, optional :param keyword_last_name: Last name :type keyword_last_name: str, optional :param keyword_title: Job title :type keyword_title: str, optional :param keyword_company: Company name :type keyword_company: str, optional :param keyword_school: School name :type keyword_school: str, optional :param connection_of: Connection of LinkedIn user, given by profile URN ID :type connection_of: str, optional :return: List of profiles (minimal data only) :rtype: list """ filters = ["resultType->PEOPLE"] if connection_of: filters.append(f"connectionOf->{connection_of}") if network_depths: filters.append(f'network->{"|".join(network_depth)}') elif network_depth: filters.append(f"network->{network_depth}") if regions: filters.append(f'geoUrn->{"|".join(regions)}') if industries: filters.append(f'industry->{"|".join(industries)}') if current_company: filters.append(f'currentCompany->{"|".join(current_company)}') if past_companies: filters.append(f'pastCompany->{"|".join(past_companies)}') if profile_languages: filters.append(f'profileLanguage->{"|".join(profile_languages)}') if nonprofit_interests: filters.append( f'nonprofitInterest->{"|".join(nonprofit_interests)}') if schools: filters.append(f'schools->{"|".join(schools)}') if service_categories: filters.append(f'serviceCategory->{"|".join(service_categories)}') # `Keywords` filter keyword_title = keyword_title if keyword_title else title if keyword_first_name: filters.append(f"firstName->{keyword_first_name}") if keyword_last_name: filters.append(f"lastName->{keyword_last_name}") if keyword_title: filters.append(f"title->{keyword_title}") if keyword_company: filters.append(f"company->{keyword_company}") if keyword_school: filters.append(f"school->{keyword_school}") params = {"filters": "List({})".format(",".join(filters))} if keywords: params["keywords"] = keywords data = self.search(params, **kwargs) results = [] for item in data: if not include_private_profiles and "publicIdentifier" not in item: continue results.append({ "urn_id": get_id_from_urn(item.get("targetUrn")), "distance": item.get("memberDistance", {}).get("value"), "public_id": item.get("publicIdentifier"), "tracking_id": get_id_from_urn(item.get("trackingUrn")), }) return results