1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/social_twitter/models/social_account.py
2024-12-10 09:04:09 +07:00

267 lines
9.6 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import requests
from werkzeug.urls import url_join
from odoo import api, fields, models, _
from odoo.exceptions import UserError
TWITTER_IMAGES_UPLOAD_ENDPOINT = "https://upload.twitter.com/1.1/media/upload.json"
class SocialAccountTwitter(models.Model):
_inherit = 'social.account'
twitter_user_id = fields.Char('Twitter User ID')
twitter_oauth_token = fields.Char('Twitter OAuth Token')
twitter_oauth_token_secret = fields.Char('Twitter OAuth Token Secret')
def _compute_statistics(self):
""" See methods '_get_last_tweets_stats' for more info about Twitter stats. """
twitter_accounts = self._filter_by_media_types(['twitter'])
super(SocialAccountTwitter, (self - twitter_accounts))._compute_statistics()
for account in twitter_accounts:
account_stats = account._get_account_stats()
last_tweets_stats = account._get_last_tweets_stats()
if account_stats and last_tweets_stats:
account.write({
'audience': account_stats.get('data', [{}])[0].get('public_metrics', {}).get('followers_count'),
'engagement': last_tweets_stats['engagement'],
'stories': last_tweets_stats['stories'],
})
def _compute_stats_link(self):
twitter_accounts = self._filter_by_media_types(['twitter'])
super(SocialAccountTwitter, (self - twitter_accounts))._compute_stats_link()
for account in twitter_accounts:
account.stats_link = f"https://analytics.twitter.com/user/{account.social_account_handle}"
@api.model_create_multi
def create(self, vals_list):
res = super(SocialAccountTwitter, self).create(vals_list)
res.filtered(lambda account: account.media_type == 'twitter')._create_default_stream_twitter()
return res
def twitter_get_user_by_username(self, username):
"""Search a user based on his username (e.g: "fpodoo").
Can not search by name, can only get user by their usernames
See: https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference
"""
user_search_endpoint = url_join(
self.env['social.media']._TWITTER_ENDPOINT,
'/2/users/by/username/%s' % username)
params = {'user.fields': 'id,name,username,description,profile_image_url'}
headers = self._get_twitter_oauth_header(
user_search_endpoint,
params=params,
method='GET'
)
response = requests.get(
user_search_endpoint,
params=params,
headers=headers,
timeout=5
)
return response.json().get('data', False) if response.ok else False
def _create_default_stream_twitter(self):
""" This will create a stream of type 'Twitter Follow' for each added accounts.
It helps with onboarding to have your tweets show up on the 'Feed' view as soon as you have configured your accounts."""
if not self:
return
own_tweets_stream_type_id = self.env.ref('social_twitter.stream_type_twitter_follow').id
streams_to_create = []
for account in self:
# we have to create a matching social.twitter.account for each stream
twitter_followed_account = self.env['social.twitter.account'].create({
'name': account.name,
'twitter_id': account.twitter_user_id,
'image': account.image
})
streams_to_create.append({
'media_id': account.media_id.id,
'stream_type_id': own_tweets_stream_type_id,
'account_id': account.id,
'twitter_followed_account_id': twitter_followed_account.id
})
self.env['social.stream'].create(streams_to_create)
def _get_account_stats(self):
""" Query the account information to retrieve the Twitter audience (= followers count). """
self.ensure_one()
twitter_account_info_url = url_join(self.env['social.media']._TWITTER_ENDPOINT, '/2/users/by')
params = {'user.fields': 'public_metrics', 'usernames': self.social_account_handle}
headers = self._get_twitter_oauth_header(
twitter_account_info_url,
params=params,
method='GET',
)
result = requests.get(
twitter_account_info_url,
params=params,
headers=headers,
timeout=5
)
if isinstance(result.json(), dict) and result.json().get('errors'):
self._action_disconnect_accounts(result.json())
return False
return result.json()
def _get_last_tweets_stats(self):
""" To properly retrieve statistics and trends, we would need an Enterprise 'Engagement API' access.
See: https://developer.twitter.com/en/docs/metrics/get-tweet-engagement/overview
Since we don't have access, we use the last 100 user tweets (max for one request) to aggregate
the data we are able to retrieve. """
self.ensure_one()
tweets_endpoint_url = url_join(
self.env['social.media']._TWITTER_ENDPOINT,
'/2/users/%s/tweets' % self.twitter_user_id)
params = {
'max_results': 100,
'tweet.fields': 'public_metrics',
}
headers = self._get_twitter_oauth_header(
tweets_endpoint_url,
params=params,
method='GET'
)
result = requests.get(
tweets_endpoint_url,
params,
headers=headers,
timeout=10,
)
if isinstance(result.json(), dict) and result.json().get('errors'):
self._action_disconnect_accounts(result.json())
return False
last_tweets_stats = {
'engagement': 0,
'stories': 0
}
for tweet in result.json().get('data', []):
public_metrics = tweet.get('public_metrics', {})
last_tweets_stats['engagement'] += public_metrics.get('like_count', 0)
last_tweets_stats['stories'] += public_metrics.get('retweet_count', 0)
return last_tweets_stats
def _get_twitter_oauth_header(self, url, headers={}, params={}, method='POST'):
self.ensure_one()
headers.update({
'oauth_token': self.twitter_oauth_token,
'oauth_token_secret': self.twitter_oauth_token_secret,
})
return self.media_id._get_twitter_oauth_header(url, headers=headers, params=params, method=method)
def _format_attachments_to_images_twitter(self, image_ids):
return self._format_images_twitter([{
'bytes': base64.decodebytes(image.datas),
'file_size': image.file_size,
'mimetype': image.mimetype
} for image in image_ids])
def _format_images_twitter(self, image_ids):
""" Twitter needs a special kind of uploading to process images.
It's done in 3 steps:
- initialize upload transaction
- send bytes
- finalize upload transaction.
More information: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload.html """
self.ensure_one()
if not image_ids:
return False
media_ids = []
for image in image_ids:
media_id = self._init_twitter_upload(image)
self._process_twitter_upload(image, media_id)
self._finish_twitter_upload(media_id)
media_ids.append(media_id)
return media_ids
def _init_twitter_upload(self, image):
data = {
'command': 'INIT',
'total_bytes': image['file_size'],
'media_category': 'tweet_gif' if image['mimetype'] == 'image/gif' else 'tweet_image',
'media_type': image['mimetype'],
}
headers = self._get_twitter_oauth_header(
TWITTER_IMAGES_UPLOAD_ENDPOINT,
params=data
)
result = requests.post(
TWITTER_IMAGES_UPLOAD_ENDPOINT,
data=data,
headers=headers,
timeout=5
)
if not result.ok:
# unfortunately Twitter does not return a proper error code so we have to rely on the error message
# last known max file size for the API is 20MB
generic_api_error = result.json().get('error', '')
raise UserError(_("We could not upload your image, it may be corrupted, it may exceed size limit or API may have send improper response (error: %s).", generic_api_error))
return result.json().get('media_id_string')
def _process_twitter_upload(self, image, media_id):
params = {
'command': 'APPEND',
'media_id': media_id,
'segment_index': 0,
}
files = {
'media': image['bytes']
}
headers = self._get_twitter_oauth_header(
TWITTER_IMAGES_UPLOAD_ENDPOINT,
params=params
)
requests.post(
TWITTER_IMAGES_UPLOAD_ENDPOINT,
params=params,
files=files,
headers=headers,
timeout=15
)
def _finish_twitter_upload(self, media_id):
data = {
'command': 'FINALIZE',
'media_id': media_id,
}
headers = self._get_twitter_oauth_header(
TWITTER_IMAGES_UPLOAD_ENDPOINT,
params=data
)
requests.post(
TWITTER_IMAGES_UPLOAD_ENDPOINT,
data=data,
headers=headers,
timeout=5
)