diff --git a/goodtimes/services.py b/goodtimes/services.py index e2faeb2..221b622 100644 --- a/goodtimes/services.py +++ b/goodtimes/services.py @@ -1,5 +1,7 @@ import random +import requests import googlemaps +import tweepy from django.conf import settings from django.core.files.uploadedfile import UploadedFile from django.core.mail import EmailMessage @@ -745,7 +747,7 @@ class GoogleMapsservice: def get_place_id_from_coordinates(self, latitude, longitude): """ Get the place ID of the given coordinates. - + :param latitude: Latitude of the location :param longitude: Longitude of the location :return: Place ID @@ -817,3 +819,268 @@ class GoogleMapsservice: # queryset = queryset.order_by(preserved_order) return queryset + + +class TwitterAPI: + def __init__(self): + self.api_key = settings.TWITTER_API_KEY + self.api_secret_key = settings.TWITTER_API_SECRET_KEY + self.access_token = settings.TWITTER_ACCESS_TOKEN + self.access_token_secret = settings.TWITTER_ACCESS_TOKEN_SECRET + self.client, self.api = self._setup_api() + + def _setup_api(self): + client = tweepy.Client( + consumer_key=self.api_key, + consumer_secret=self.api_secret_key, + access_token=self.access_token, + access_token_secret=self.access_token_secret, + ) + auth = tweepy.OAuth1UserHandler( + self.api_key, + self.api_secret_key, + self.access_token, + self.access_token_secret, + ) + api = tweepy.API(auth, wait_on_rate_limit=True) + return client, api + + def post_text_tweet(self, caption): + tweet = self.client.create_tweet(text=caption) + return tweet + + def post_image_with_caption(self, image_url, caption): + media = self.api.media_upload(image_url) + tweet = self.client.create_tweet(text=caption, media_ids=[media.media_id]) + return tweet + + +class TwitterPoster: + def __init__(self, twitter_api): + self.twitter_api = twitter_api + + def post_text_tweet(self, caption): + try: + tweet = self.twitter_api.post_text_tweet(caption) + return {'success': True, 'message': 'Tweet posted successfully!'} + except tweepy.TweepyException as e: + return {'success': False, 'message': f'Error posting tweet: {e}'} + + def post_image_with_caption(self, image_url, caption): + try: + tweet = self.twitter_api.post_image_with_caption(image_url, caption) + return {'success': True, 'message': 'Tweet posted successfully!'} + except tweepy.TweepyException as e: + return {'success': False, 'message': f'Error posting tweet: {e}'} + +class FacebookAPI: + def __init__(self): + self.app_id = settings.FACEBOOK_APP_ID + self.app_secret = settings.FACEBOOK_APP_SECRET + self.page_id = settings.FACEBOOK_PAGE_ID + self.page_access_token = None + + def _get_short_lived_user_access_token(self): + try: + url = f"https://graph.facebook.com/oauth/access_token?grant_type=client_credentials&client_id={self.app_id}&client_secret={self.app_secret}" + response = requests.get(url) + response.raise_for_status() + print(f"short lived token {response.json()}") + return response.json()['access_token'] + except requests.exceptions.RequestException as e: + print(f"Error getting short-lived user access token: {e}") + return None + + def _get_long_lived_user_access_token(self, short_lived_token): + try: + url = f"https://graph.facebook.com/v20.0/oauth/access_token?grant_type=fb_exchange_token&client_id={self.app_id}&client_secret={self.app_secret}&fb_exchange_token={short_lived_token}" + response = requests.get(url) + response.raise_for_status() + print(f"long lived access token : {response.json()}") + return response.json()['access_token'] + except requests.exceptions.RequestException as e: + print(f"Error getting long-lived user access token: {e}") + return None + + def _get_page_access_token(self, long_lived_token): + url = f"https://graph.facebook.com/{self.page_id}?fields=access_token&access_token={long_lived_token}" + response = requests.get(url) + # response.raise_for_status() + print(f"page access token is {response.json()}") + # self.page_access_token = response.json()["access_token"] + + def authenticate(self): + # short_lived_token = self._get_short_lived_user_access_token() + # if not short_lived_token: + # return False + # long_lived_token = self._get_long_lived_user_access_token(short_lived_token) + # if not long_lived_token: + # return False + # self._get_page_access_token(short_lived_token) + self.page_access_token = settings.FACEBOOK_ACCESS_TOKEN + return True + + def post_photo(self, image_url, caption): + if not self.page_access_token: + print("Page access token not obtained. Call authenticate() first.") + return False + try: + url = f"https://graph.facebook.com/v20.0/{self.page_id}/photos" + params = { + "message": caption, + "url": image_url, + "access_token": self.page_access_token, + } + response = requests.post(url, params=params) + response.raise_for_status() + result = response.json() + if "id" not in result: + print(f"Error posting photo: {result}") + return False + print(f"Data posted successfully. Post Id: {result['id']}") + return True + except requests.exceptions.RequestException as e: + print(f"Error posting photo: {e}") + return False + +class FacebookPoster: + def __init__(self, facebook_api): + self.facebook_api = facebook_api + + def post_photo(self, image_url, caption): + if not self.facebook_api.authenticate(): + print("Authentication failed. Please try again.") + return {'success': False, 'message': 'Error posting photo. Authenticate failed'} + result = self.facebook_api.post_photo(image_url, caption) + if not result: + return {'success': False, 'message': 'Error posting photo'} + return {'success': True, 'message': 'Photo posted successfully'} + + + +# import requests + +# app_id = "YOUR_APP_ID" +# app_secret = "YOUR_APP_SECRET" +# page_id = "YOUR_PAGE_ID" # You need to specify the page ID +# # Step 1: Get an App Access Token +# response = requests.get(f"https://graph.facebook.com/oauth/access_token?client_id={app_id}&client_secret={app_secret}&grant_type=client_credentials") +# app_access_token = response.json()["access_token"] + +# # Step 2: Get a Page Access Token +# response = requests.get(f"https://graph.facebook.com/{page_id}?fields=access_token&access_token={app_access_token}") +# page_access_token = response.json()["access_token"] + +# # Use the Page Access Token to query the Page node +# response = requests.get(f"https://graph.facebook.com/{page_id}?access_token={page_access_token}") +# page_data = response.json() +# print(page_data) +class InstagramAPI: + def __init__(self): + self.app_id = settings.FACEBOOK_APP_ID + self.app_secret = settings.FACEBOOK_APP_SECRET + self.page_id = settings.INSTAGRAM_PAGE_ID + self.page_access_token = None + + def _get_short_lived_user_access_token(self): + try: + url = f"https://graph.facebook.com/oauth/access_token" + params = { + "grant_type": "client_credentials", + "client_id": self.app_id, + "client_secret": self.app_secret + } + response = requests.get(url, params=params) + response.raise_for_status() + print(f"Short-lived token: {response.json()}") + return response.json()['access_token'] + except requests.exceptions.RequestException as e: + print(f"Error getting short-lived user access token: {e}") + return None + + def _get_long_lived_user_access_token(self, short_lived_token): + try: + url = f"https://graph.facebook.com/v20.0/oauth/access_token" + params = { + "grant_type": "fb_exchange_token", + "client_id": self.app_id, + "client_secret": self.app_secret, + "fb_exchange_token": short_lived_token + } + response = requests.get(url, params=params) + response.raise_for_status() + print(f"Long-lived access token: {response.json()}") + return response.json()['access_token'] + except requests.exceptions.RequestException as e: + print(f"Error getting long-lived user access token: {e}") + return None + + def _get_page_access_token(self, long_lived_token): + try: + url = f"https://graph.facebook.com/{self.page_id}" + params = { + "fields": "access_token", + "access_token": long_lived_token + } + response = requests.get(url, params=params) + response.raise_for_status() + print(f"Page access token: {response.json()}") + return response.json()["access_token"] + except requests.exceptions.RequestException as e: + print(f"Error getting page access token: {e}") + return None + + def authenticate(self): + # short_lived_token = self._get_short_lived_user_access_token() + # if not short_lived_token: + # return False + # long_lived_token = self._get_long_lived_user_access_token(short_lived_token) + # if not long_lived_token: + # return False + # self.page_access_token = self._get_page_access_token(long_lived_token) + self.page_access_token = settings.FACEBOOK_ACCESS_TOKEN + return True + + def post_image_with_caption(self, image_path, caption): + image_path="https://admin.goodtimesltd.co.uk/static/img/goodtimes.png" + if not self.page_access_token: + print("Page access token not obtained. Call Authenticate() first.") + return False + try: + url = f"https://graph.facebook.com/v20.0/{self.page_id}/media" + params = { + "caption": caption, + "image_url": image_path, + "access_token": self.page_access_token + } + response = requests.post(url, data=params) + response.raise_for_status() + result = response.json() + print(f"Post image with caption result: {result['id']}") + + url = f"https://graph.facebook.com/v20.0/{self.page_id}/media_publish" + params = { + "creation_id": result["id"], + "access_token": self.page_access_token + } + response = requests.post(url, params=params) + response.raise_for_status() + result = response.json() + return True + except requests.exceptions.RequestException as e: + print(f"Error posting photo on instagram: {e}") + return False + + +class InstagramPoster: + def __init__(self, instagram_api): + self.instagram_api = instagram_api + + def post_image_with_caption(self, image_path, caption): + if not self.instagram_api.authenticate(): + print("Instagram API authentication failed.") + return {'success': False, 'message': 'Error posting photo. Authenticate failed'} + result = self.instagram_api.post_image_with_caption(image_path, caption) + if not result: + return {'success': False, 'message': 'Error posting photo.'} + return {'success': True, 'message': 'Photo posted successfully'} \ No newline at end of file diff --git a/goodtimes/settings/base.py b/goodtimes/settings/base.py index 5476155..b6abe36 100644 --- a/goodtimes/settings/base.py +++ b/goodtimes/settings/base.py @@ -339,3 +339,18 @@ PLACES_MAPS_API_KEY = env.str("GOOGLE_MAPS_API_KEY") PLACES_MAP_WIDGET_HEIGHT = 480 PLACES_MAP_OPTIONS = '{"center": { "lat": 38.971584, "lng": -95.235072 }, "zoom": 10}' PLACES_MARKER_OPTIONS = '{"draggable": true}' + +# twitter keys +TWITTER_API_KEY = env.str("TWITTER_API_KEY") +TWITTER_API_SECRET_KEY = env.str("TWITTER_API_SECRET_KEY") +TWITTER_ACCESS_TOKEN = env.str("TWITTER_ACCESS_TOKEN") +TWITTER_ACCESS_TOKEN_SECRET = env.str("TWITTER_ACCESS_TOKEN_SECRET") + +# facebook keys +FACEBOOK_APP_ID = env.str("FACEBOOK_APP_ID") +FACEBOOK_APP_SECRET = env.str("FACEBOOK_APP_SECRET") +FACEBOOK_PAGE_ID = env.str("FACEBOOK_PAGE_ID") +FACEBOOK_ACCESS_TOKEN = env.str("FACEBOOK_ACCESS_TOKEN") + +# Instagram Key +INSTAGRAM_PAGE_ID = env.str('INSTAGRAM_PAGE_ID') diff --git a/manage_events/management/commands/test_facebook_api.py b/manage_events/management/commands/test_facebook_api.py new file mode 100644 index 0000000..efbc3c5 --- /dev/null +++ b/manage_events/management/commands/test_facebook_api.py @@ -0,0 +1,31 @@ +import os +from django.conf import settings +from django.core.management.base import BaseCommand +from goodtimes.services import FacebookAPI, FacebookPoster +from ...models import Event + +class Command(BaseCommand): + help = 'Test facebook posting functionality' + + def handle(self, *args, **kwargs): + event = Event.objects.get(id=20) + if not event: + self.stdout.write(self.style.ERROR("No event found.")) + + if not event.image: + self.stdout.write(self.style.ERROR("No image found.")) + + base_domain = settings.BASE_DOMAIN + image_path = f"{base_domain}{event.image.url}" + print(f"complete path of image {image_path}") + caption = f"{event.title}\nDuration: {event.start_date} to {event.end_date}\nAddress: {event.venue.address}" + + facebook_api = FacebookAPI() + facebook_poster = FacebookPoster(facebook_api) + + response = facebook_poster.post_photo(image_path, caption) + + if response['success']: + self.stdout.write(self.style.SUCCESS(response['message'])) + else: + self.stdout.write(self.style.ERROR(response['message'])) diff --git a/manage_events/management/commands/test_instagram_api.py b/manage_events/management/commands/test_instagram_api.py new file mode 100644 index 0000000..5717284 --- /dev/null +++ b/manage_events/management/commands/test_instagram_api.py @@ -0,0 +1,34 @@ +import os +from django.conf import settings +from django.core.management.base import BaseCommand +from goodtimes.services import InstagramAPI, InstagramPoster +from ...models import Event +import urllib.request + +class Command(BaseCommand): + help = 'Test Instagram posting functionality' + + def handle(self, *args, **kwargs): + event = Event.objects.get(id=20) + if not event: + self.stdout.write(self.style.ERROR("No event found.")) + + if not event.image: + self.stdout.write(self.style.ERROR("No image found.")) + + # base_domain = settings.BASE_DOMAIN + # image_path = f"{base_domain}{event.image.url}" + image_path = event.image.url + print(f"complete path of image {image_path}") + + caption = f"{event.title}\nDuration: {event.start_date} to {event.end_date}\nAddress: {event.venue.address}" + + instagram_api = InstagramAPI() + instagram_poster = InstagramPoster(instagram_api) + + response = instagram_poster.post_image_with_caption(image_path, caption) + + if response['success']: + self.stdout.write(self.style.SUCCESS(response['message'])) + else: + self.stdout.write(self.style.ERROR(response['message'])) diff --git a/manage_events/management/commands/test_twitter_api.py b/manage_events/management/commands/test_twitter_api.py new file mode 100644 index 0000000..2c79553 --- /dev/null +++ b/manage_events/management/commands/test_twitter_api.py @@ -0,0 +1,28 @@ +import os +from django.core.management.base import BaseCommand +from goodtimes.services import TwitterAPI, TwitterPoster +from ...models import Event + +class Command(BaseCommand): + help = 'Test Twitter posting functionality' + + def handle(self, *args, **kwargs): + event = Event.objects.get(id=19) + if not event: + self.stdout.write(self.style.ERROR("No event found.")) + + if not event.image: + self.stdout.write(self.style.ERROR("No image found.")) + + image_path = event.image.path + caption = f"{event.title}\nDuration: {event.start_date} to {event.end_date}\nAddress: {event.venue.address}" + + twitter_api = TwitterAPI() + twitter_poster = TwitterPoster(twitter_api) + + response = twitter_poster.post_image_with_caption(image_path, caption) + + if response['success']: + self.stdout.write(self.style.SUCCESS(response['message'])) + else: + self.stdout.write(self.style.ERROR(response['message'])) diff --git a/manage_events/urls.py b/manage_events/urls.py index bcc3182..1f52b7b 100644 --- a/manage_events/urls.py +++ b/manage_events/urls.py @@ -99,4 +99,6 @@ urlpatterns = [ views.GenerateEventReportView.as_view(), name="generate_event_report", ), + + path("post-to-social-media///", views.SocialMediaPostView.as_view(), name="social_media_post") ] diff --git a/manage_events/views.py b/manage_events/views.py index 45184b1..2381ede 100644 --- a/manage_events/views.py +++ b/manage_events/views.py @@ -1,5 +1,6 @@ from django.shortcuts import get_object_or_404, redirect, render from accounts import resource_action +from goodtimes.services import FacebookAPI, FacebookPoster, InstagramAPI, InstagramPoster, TwitterAPI, TwitterPoster from goodtimes.utils import JsonResponseUtil from manage_events.api.serializers import VenueSerializer, VenueShortSerializer from manage_events.forms import ( @@ -553,3 +554,52 @@ class GenerateEventReportView(generic.View): response["Content-Disposition"] = f'attachment; filename="{filename}"' return response + +class SocialMediaPostView(generic.View): + def get(self, request, *args, **kwargs): + platform = kwargs.get("platform") + event_id = kwargs.get("id") + print(platform, event_id) + errors = [] + + try: + event = Event.objects.get(id=event_id) + except Event.DoesNotExist: + errors.append("Event does not exist") + return JsonResponseUtil.error(message=errors, errors=errors) + + if not event.active: + errors.append("Event is not active") + return JsonResponseUtil.error(message=errors, errors=errors) + + caption = f"{event.title}\nDuration: {event.start_date} to {event.end_date}\nAddress: {event.venue.address}" + print(f"image url and caption is {caption}") + if platform in ['instagram', 'facebook', 'twitter', 'all']: + + if platform in ['twitter', 'all']: + image_url = event.image.path + twitter_api = TwitterAPI() + twitter_poster = TwitterPoster(twitter_api) + result = twitter_poster.post_image_with_caption(image_url, caption) + if not result['success']: + errors.append(result['message']) + + image_url = request.build_absolute_uri(event.image.url) # fb and insta require complete path with domain + if platform in ['facebook', 'all']: + facebook_api = FacebookAPI() + facebook_poster = FacebookPoster(facebook_api) + result = facebook_poster.post_photo(image_url, caption) + if not result["success"]: + errors.append(result["message"]) + + if platform in ['instagram', 'all']: + instagram_api = InstagramAPI() + instagram_poster = InstagramPoster(instagram_api) + result = instagram_poster.post_image_with_caption(image_url, caption) + if not result["success"]: + errors.append(result["message"]) + + if not errors: + return JsonResponseUtil.success(message='Post Successful') + + return JsonResponseUtil.error(message=errors, errors=errors) diff --git a/requirements.txt b/requirements.txt index 3d196bc..f3ae9d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -69,8 +69,9 @@ sniffio==1.3.1 sqlparse==0.4.4 stripe==8.2.0 tqdm==4.66.2 +tweepy==4.14.0 Twisted==23.10.0 -# twisted-iocpsupport==1.0.4 +twisted-iocpsupport==1.0.4 txaio==23.1.1 typing_extensions==4.9.0 tzdata==2024.1 diff --git a/static/img/facebook.png b/static/img/facebook.png new file mode 100644 index 0000000..8400a87 Binary files /dev/null and b/static/img/facebook.png differ diff --git a/static/img/forward_all_icon.png b/static/img/forward_all_icon.png new file mode 100644 index 0000000..6b6f4d1 Binary files /dev/null and b/static/img/forward_all_icon.png differ diff --git a/static/img/instagram.png b/static/img/instagram.png new file mode 100644 index 0000000..b61a965 Binary files /dev/null and b/static/img/instagram.png differ diff --git a/static/img/x_twitter.png b/static/img/x_twitter.png new file mode 100644 index 0000000..62887fd Binary files /dev/null and b/static/img/x_twitter.png differ diff --git a/templates/manage_events/event_details.html b/templates/manage_events/event_details.html index e6fff65..1fa11b3 100644 --- a/templates/manage_events/event_details.html +++ b/templates/manage_events/event_details.html @@ -2,12 +2,48 @@ {% load static %} {% block stylesheet %} + {% include "cdn_through_html/filepond_cdn_css.html" %} {% include "cdn_through_html/quill_cdn_css.html" %} {% include "cdn_through_html/tagify_cdn_css.html" %} +{% include "cdn_through_html/sweetalert2_cdn_css.html" %} {{form.media}} + + {% endblock %} {% block content %} @@ -25,15 +61,41 @@ {% endif %} -
-
-

{{ event.brand.title }}

-

{{ event.title }}

-

Description: {{ event.description }}

-

Created By: {{ event.created_by }}

+
+
+
+
+

{{ event.brand.title }}

+

{{ event.title }}

+

Description: {{ event.description }}

+

Created By: {{ event.created_by }}

+
+
+
+
+
+ +
+ +
@@ -145,4 +207,78 @@
-{% endblock content %} \ No newline at end of file +{% endblock content %} + +{% block javascript %} + + {% include "cdn_through_html/sweetalert2_cdn_js.html" %} + +{%endblock javascript%} \ No newline at end of file