from flask import current_app import requests from requests_oauthlib import OAuth2Session from urllib.parse import urlencode class LinkedInService: """Service for LinkedIn API integration.""" def __init__(self): self.client_id = current_app.config['CLIENT_ID'] self.client_secret = current_app.config['CLIENT_SECRET'] self.redirect_uri = current_app.config['REDIRECT_URL'] self.scope = ['openid', 'profile', 'email', 'w_member_social'] def get_authorization_url(self, state: str) -> str: """ Get LinkedIn authorization URL. Args: state (str): State parameter for security Returns: str: Authorization URL """ linkedin = OAuth2Session( self.client_id, redirect_uri=self.redirect_uri, scope=self.scope, state=state ) authorization_url, _ = linkedin.authorization_url( 'https://www.linkedin.com/oauth/v2/authorization' ) return authorization_url def get_access_token(self, code: str) -> dict: """ Exchange authorization code for access token. Args: code (str): Authorization code Returns: dict: Token response """ url = "https://www.linkedin.com/oauth/v2/accessToken" headers = { "Content-Type": "application/x-www-form-urlencoded" } data = { "grant_type": "authorization_code", "code": code, "redirect_uri": self.redirect_uri, "client_id": self.client_id, "client_secret": self.client_secret } response = requests.post(url, headers=headers, data=data) response.raise_for_status() return response.json() def get_user_info(self, access_token: str) -> dict: """ Get user information from LinkedIn. Args: access_token (str): LinkedIn access token Returns: dict: User information """ url = "https://api.linkedin.com/v2/userinfo" headers = { "Authorization": f"Bearer {access_token}" } response = requests.get(url, headers=headers) response.raise_for_status() return response.json() def publish_post(self, access_token: str, user_id: str, text_content: str, image_url: str = None) -> dict: """ Publish a post to LinkedIn. Args: access_token (str): LinkedIn access token user_id (str): LinkedIn user ID text_content (str): Post content image_url (str, optional): Image URL Returns: dict: Publish response """ url = "https://api.linkedin.com/v2/ugcPosts" headers = { "Authorization": f"Bearer {access_token}", "X-Restli-Protocol-Version": "2.0.0", "Content-Type": "application/json" } if image_url: # Handle image upload register_body = { "registerUploadRequest": { "recipes": ["urn:li:digitalmediaRecipe:feedshare-image"], "owner": f"urn:li:person:{user_id}", "serviceRelationships": [{ "relationshipType": "OWNER", "identifier": "urn:li:userGeneratedContent" }] } } r = requests.post( "https://api.linkedin.com/v2/assets?action=registerUpload", headers=headers, json=register_body ) if r.status_code not in (200, 201): raise Exception(f"Failed to register upload: {r.status_code} {r.text}") datar = r.json()["value"] upload_url = datar["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"] asset_urn = datar["asset"] # Upload image upload_headers = { "Authorization": f"Bearer {access_token}", "X-Restli-Protocol-Version": "2.0.0", "Content-Type": "application/octet-stream" } # Download image and upload to LinkedIn image_response = requests.get(image_url) if image_response.status_code == 200: upload_response = requests.put(upload_url, headers=upload_headers, data=image_response.content) if upload_response.status_code not in (200, 201): raise Exception(f"Failed to upload image: {upload_response.status_code} {upload_response.text}") # Create post with image post_body = { "author": f"urn:li:person:{user_id}", "lifecycleState": "PUBLISHED", "specificContent": { "com.linkedin.ugc.ShareContent": { "shareCommentary": {"text": text_content}, "shareMediaCategory": "IMAGE", "media": [{ "status": "READY", "media": asset_urn, "description": {"text": "Post image"}, "title": {"text": "Post image"} }] } }, "visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"} } else: # Create text-only post post_body = { "author": f"urn:li:person:{user_id}", "lifecycleState": "PUBLISHED", "specificContent": { "com.linkedin.ugc.ShareContent": { "shareCommentary": { "text": text_content }, "shareMediaCategory": "NONE" } }, "visibility": { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" } } response = requests.post(url, headers=headers, json=post_body) response.raise_for_status() return response.json()