1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/social_facebook/controllers/main.py
2024-12-10 09:04:09 +07:00

249 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import json
import logging
import requests
import urllib.parse
import werkzeug
from odoo import http, _
from odoo.http import request
from odoo.addons.auth_oauth.controllers.main import fragment_to_query_string
from odoo.addons.social.controllers.main import SocialController
from odoo.addons.social.controllers.main import SocialValidationException
from werkzeug.urls import url_encode, url_join
_logger = logging.getLogger(__name__)
class SocialFacebookController(SocialController):
# ========================================================
# ACCOUNTS MANAGEMENT
# ========================================================
@http.route(['/social_facebook/callback'], type='http', auth='user')
@fragment_to_query_string
def social_facebook_account_callback(self, access_token=None, is_extended_token=False, **kw):
""" Facebook returns to the callback URL with all its own arguments as hash parameters.
We use this very handy 'fragment_to_query_string' decorator to convert them to server readable parameters. """
if not request.env.user.has_group('social.group_social_manager'):
return request.render('social.social_http_error_view',
{'error_message': _('Unauthorized. Please contact your administrator. ')})
if kw.get('error') != 'access_denied':
if not access_token:
return request.render(
'social.social_http_error_view',
{'error_message': _('Facebook did not provide a valid access token.')})
if access_token:
media = request.env.ref('social_facebook.social_media_facebook')
try:
self._facebook_create_accounts(access_token, media, is_extended_token)
except SocialValidationException as e:
return request.render('social.social_http_error_view', {'error_message': e.get_message(), 'documentation_data': e.get_documentation_data()})
url = '/web?#%s' % url_encode({
'action': request.env.ref('social.action_social_stream_post').id,
'view_type': 'kanban',
'model': 'social.stream.post',
})
return request.redirect(url)
# ========================================================
# COMMENTS / LIKES
# ========================================================
@http.route('/social_facebook/comment', type='http', auth='user', methods=['POST'])
def social_facebook_add_comment(self, stream_post_id=None, message=None, comment_id=None, existing_attachment_id=None, is_edit=False, **kwargs):
stream_post = self._get_social_stream_post(stream_post_id, 'facebook')
attachment = None
files = request.httprequest.files.getlist('attachment')
if files and files[0]:
attachment = files[0]
if comment_id and is_edit:
result = stream_post._facebook_comment_post(
url_join(request.env['social.media']._FACEBOOK_ENDPOINT_VERSIONED, comment_id),
message,
existing_attachment_id=existing_attachment_id,
attachment=attachment
)
elif comment_id:
result = stream_post._facebook_comment_post(
url_join(request.env['social.media']._FACEBOOK_ENDPOINT_VERSIONED, "%s/comments" % (comment_id)),
message,
existing_attachment_id=existing_attachment_id,
attachment=attachment
)
else:
result = stream_post._facebook_comment_post(
url_join(request.env['social.media']._FACEBOOK_ENDPOINT_VERSIONED, "%s/comments" % (stream_post.facebook_post_id)),
message,
existing_attachment_id=existing_attachment_id,
attachment=attachment
)
result['formatted_created_time'] = request.env['social.stream.post']._format_facebook_published_date(result)
return json.dumps(result)
@http.route('/social_facebook/delete_comment', type='json', auth='user')
def social_facebook_delete_comment(self, stream_post_id, comment_id):
stream_post = self._get_social_stream_post(stream_post_id, 'facebook')
stream_post._facebook_comment_delete(comment_id)
@http.route('/social_facebook/get_comments', type='json', auth='user')
def social_facebook_get_comments(self, stream_post_id, next_records_token=False, comments_count=20):
stream_post = self._get_social_stream_post(stream_post_id, 'facebook')
return stream_post._facebook_comment_fetch(next_records_token, count=comments_count)
@http.route('/social_facebook/like_comment', type='json', auth='user')
def social_facebook_like_comment(self, stream_post_id, comment_id, like):
stream_post = self._get_social_stream_post(stream_post_id, 'facebook')
stream_post._facebook_like(comment_id, like)
@http.route('/social_facebook/like_post', type='json', auth='user')
def social_facebook_like_post(self, stream_post_id, like):
stream_post = self._get_social_stream_post(stream_post_id, 'facebook')
stream_post._facebook_like(stream_post.facebook_post_id, like)
# ========================================================
# MISC / UTILITY
# ========================================================
@http.route(['/social_facebook/redirect_to_profile/<int:account_id>/<facebook_user_id>'], type='http', auth='user')
def social_facebook_redirect_to_profile(self, account_id, facebook_user_id, name=''):
"""
All profiles are not available through a direct link so we need to
- Try to get a direct link to their profile
- If we can't, we perform a search on Facebook with their name
"""
account = request.env['social.account'].browse(account_id)
json_response = requests.get(
url_join(request.env['social.media']._FACEBOOK_ENDPOINT_VERSIONED, facebook_user_id),
params={
'fields': 'name,link',
'access_token': account.facebook_access_token
},
timeout=5
).json()
profile_url = json_response.get('link')
if profile_url:
redirect_url = profile_url
else:
redirect_url = 'https://www.facebook.com/search/?q=%s' % urllib.parse.quote(name)
return request.redirect(redirect_url, local=False)
def _facebook_create_accounts(self, access_token, media, is_extended_token):
""" Steps to create the facebook social.accounts:
1. Fetch an extended access token (see '_facebook_get_extended_access_token' for more info)
2. Query the accounts api with that token
3. Extract the 'pages' contained in the json result
4. Create a social.account with its associated token (each page has its own access token)
4a. If the social.account was already created before, we refresh its access_token """
extended_access_token = access_token if is_extended_token else self._facebook_get_extended_access_token(access_token, media)
accounts_url = url_join(request.env['social.media']._FACEBOOK_ENDPOINT, "/me/accounts/")
json_response = requests.get(accounts_url,
params={
'fields': 'id,access_token,name,username',
'access_token': extended_access_token
},
timeout=5
).json()
if 'data' not in json_response:
raise SocialValidationException(_('Facebook did not provide a valid access token or it may have expired.'))
if not json_response['data']:
message = _('You need to be the manager of a Facebook Page to post with Odoo Social.\n Please create one and make sure it is linked to your account.')
documentation_link = 'https://facebook.com/business/pages/manage'
documentation_link_label = _('Read More about Facebook Pages')
documentation_link_icon_class = 'fa fa-facebook'
raise SocialValidationException(message, documentation_link, documentation_link_label, documentation_link_icon_class)
accounts_to_create = []
existing_accounts = self._facebook_get_existing_accounts(media, json_response)
for account in json_response.get('data'):
account_id = account['id']
access_token = account.get('access_token')
social_account_handle = account.get('username')
if existing_accounts.get(account_id):
# update access token
# TODO awa: maybe check for name/picture update?
existing_accounts.get(account_id).write({
'active': True,
'facebook_access_token': access_token,
'social_account_handle': social_account_handle,
'is_media_disconnected': False
})
else:
accounts_to_create.append({
'name': account.get('name'),
'media_id': media.id,
'has_trends': True,
'facebook_account_id': account_id,
'facebook_access_token': access_token,
'social_account_handle': social_account_handle,
'image': self._facebook_get_profile_image(account_id)
})
if accounts_to_create:
request.env['social.account'].create(accounts_to_create)
def _facebook_get_extended_access_token(self, access_token, media):
""" The way that it works is Facebook sends you a token that is valid for 2 hours
that you can automatically 'extend' to a 60 days token using the oauth/access_token endpoint.
After those 60 days, there is absolutely no way to renew the token automatically, we have to ask
the user's permissions again manually.
However, using this extended token with the 'manage_pages' permission allows receiving 'Page Access Tokens'
that are valid forever.
More details on this mechanic: https://www.devils-heaven.com/facebook-access-tokens/ """
facebook_app_id = request.env['ir.config_parameter'].sudo().get_param('social.facebook_app_id')
facebook_client_secret = request.env['ir.config_parameter'].sudo().get_param('social.facebook_client_secret')
extended_token_url = url_join(request.env['social.media']._FACEBOOK_ENDPOINT, "/oauth/access_token")
extended_token_request = requests.post(extended_token_url,
params={
'client_id': facebook_app_id,
'client_secret': facebook_client_secret,
'grant_type': 'fb_exchange_token',
'fb_exchange_token': access_token
},
timeout=5
)
return extended_token_request.json().get('access_token')
def _facebook_get_profile_image(self, account_id):
profile_image_url = url_join(request.env['social.media']._FACEBOOK_ENDPOINT_VERSIONED, '%s/picture?height=300' % account_id)
return base64.b64encode(requests.get(profile_image_url, timeout=10).content)
def _facebook_get_existing_accounts(self, media_id, json_response):
""" Returns the social.accounts already created as:
{ facebook_account_id: social.account } """
facebook_accounts_ids = [account['id'] for account in json_response.get('data', [])]
if facebook_accounts_ids:
existing_accounts = request.env['social.account'].sudo().with_context(active_test=False).search([
('media_id', '=', int(media_id)),
('facebook_account_id', 'in', facebook_accounts_ids)
])
error_message = existing_accounts._get_multi_company_error_message()
if error_message:
raise SocialValidationException(error_message)
return {
existing_account.facebook_account_id: existing_account
for existing_account in existing_accounts
}
return {}