forked from Mapan/odoo17e
202 lines
8.8 KiB
Python
202 lines
8.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import re
|
|
import dateutil.parser
|
|
import requests
|
|
|
|
from odoo import models, fields, api
|
|
from werkzeug.urls import url_join
|
|
|
|
|
|
class SocialStreamFacebook(models.Model):
|
|
_inherit = 'social.stream'
|
|
|
|
def _apply_default_name(self):
|
|
facebook_streams = self.filtered(lambda s: s.media_id.media_type == 'facebook')
|
|
super(SocialStreamFacebook, (self - facebook_streams))._apply_default_name()
|
|
|
|
for stream in facebook_streams:
|
|
stream.write({'name': '%s: %s' % (stream.stream_type_id.name, stream.account_id.name)})
|
|
|
|
def _fetch_stream_data(self):
|
|
if self.media_id.media_type != 'facebook':
|
|
return super(SocialStreamFacebook, self)._fetch_stream_data()
|
|
|
|
if self.stream_type_id.stream_type == 'facebook_page_posts':
|
|
return self._fetch_page_posts('published_posts')
|
|
elif self.stream_type_id.stream_type == 'facebook_page_mentions':
|
|
return self._fetch_page_posts('tagged')
|
|
|
|
def _fetch_page_posts(self, endpoint_name):
|
|
self.ensure_one()
|
|
|
|
facebook_fields = [
|
|
'id',
|
|
'message',
|
|
'from',
|
|
'shares',
|
|
'likes.limit(1).summary(true)',
|
|
'comments.limit(10).summary(true){message,from,like_count}',
|
|
'attachments',
|
|
'created_time',
|
|
'message_tags'
|
|
]
|
|
if endpoint_name == 'published_posts':
|
|
facebook_fields.append('insights.metric(post_impressions)')
|
|
|
|
posts_endpoint_url = url_join(self.env['social.media']._FACEBOOK_ENDPOINT_VERSIONED, "%s/%s" % (self.account_id.facebook_account_id, endpoint_name))
|
|
result = requests.get(posts_endpoint_url,
|
|
params={
|
|
'access_token': self.account_id.facebook_access_token,
|
|
'fields': ','.join(facebook_fields)
|
|
},
|
|
timeout=5
|
|
)
|
|
|
|
result_posts = result.json().get('data')
|
|
if not result_posts:
|
|
self.account_id._action_disconnect_accounts(result.json())
|
|
return False
|
|
|
|
facebook_post_ids = [post.get('id') for post in result_posts]
|
|
existing_posts = self.env['social.stream.post'].search([
|
|
('stream_id', '=', self.id),
|
|
('facebook_post_id', 'in', facebook_post_ids)
|
|
])
|
|
existing_posts_by_facebook_post_id = {
|
|
post.facebook_post_id: post for post in existing_posts
|
|
}
|
|
|
|
posts_to_create = []
|
|
for post in result_posts:
|
|
values = {
|
|
'stream_id': self.id,
|
|
'message': self._format_facebook_message(post.get('message'), post.get('message_tags')),
|
|
'author_name': post.get('from', {}).get('name', ''),
|
|
'facebook_author_id': post.get('from', {}).get('id'),
|
|
'published_date': dateutil.parser.parse(post.get('created_time'), ignoretz=True),
|
|
'facebook_shares_count': post.get('shares', {}).get('count'),
|
|
'facebook_likes_count': post.get('likes', {}).get('summary', {}).get('total_count'),
|
|
'facebook_user_likes': post.get('likes', {}).get('summary', {}).get('has_liked'),
|
|
'facebook_comments_count': post.get('comments', {}).get('summary', {}).get('total_count'),
|
|
'facebook_reach': post.get('insights', {}).get('data', [{}])[0].get('values', [{}])[0].get('value'),
|
|
'facebook_post_id': post.get('id'),
|
|
'facebook_is_event_post': post.get('attachments', {}).get('data', [{}])[0].get('type') == 'event',
|
|
}
|
|
|
|
attachments = self._extract_facebook_attachments(post)
|
|
existing_post = existing_posts_by_facebook_post_id.get(post.get('id'))
|
|
if existing_post:
|
|
if attachments.get('stream_post_image_ids'):
|
|
values['stream_post_image_ids'] = [(5, 0, 0)] + attachments['stream_post_image_ids']
|
|
existing_post.sudo().write(values)
|
|
else:
|
|
if attachments or values['message']:
|
|
# do not create post without content
|
|
values.update(attachments)
|
|
posts_to_create.append(values)
|
|
|
|
stream_posts = self.env['social.stream.post'].sudo().create(posts_to_create)
|
|
return any(stream_post.stream_id.create_uid.id == self.env.uid for stream_post in stream_posts)
|
|
|
|
@api.model
|
|
def _extract_facebook_attachments(self, post):
|
|
result = {}
|
|
|
|
for attachment in post.get('attachments', {}).get('data', []):
|
|
if attachment.get('type') == 'share':
|
|
result.update({
|
|
'link_title': attachment.get('title'),
|
|
'link_description': attachment.get('description'),
|
|
'link_url': attachment.get('url'),
|
|
})
|
|
|
|
if attachment.get('media'):
|
|
result.update({
|
|
'link_image_url': attachment.get('media').get('image').get('src')
|
|
})
|
|
elif attachment.get('type') == 'album':
|
|
images = []
|
|
images_urls = []
|
|
for sub_image in attachment.get('subattachments', {}).get('data', []):
|
|
image_url = sub_image.get('media').get('image').get('src')
|
|
images.append({
|
|
'image_url': image_url
|
|
})
|
|
images_urls.append(image_url)
|
|
|
|
if images:
|
|
result.update({
|
|
'stream_post_image_ids': [(0, 0, attachment) for attachment in images],
|
|
})
|
|
elif attachment.get('type') in ['photo', 'animated_image_video']:
|
|
# TODO improvement later: handle videos in Feed view to correctly display FB GIFs?
|
|
image_src = attachment.get('media', {}).get('image', {}).get('src')
|
|
if image_src:
|
|
result.update({'stream_post_image_ids': [(0, 0, {'image_url': image_src})]})
|
|
|
|
elif attachment.get('type') == 'event':
|
|
# events creation post are handle like link and image in the frontend
|
|
result.update({
|
|
'link_title': attachment.get('title'),
|
|
'link_description': attachment.get('description'),
|
|
'link_url': attachment.get('target', {}).get('url', ''),
|
|
})
|
|
|
|
image_src = attachment.get('media', {}).get('image', {}).get('src')
|
|
if image_src:
|
|
result.update({'stream_post_image_ids': [(0, 0, {'image_url': image_src})]})
|
|
|
|
return result
|
|
|
|
@api.model
|
|
def _format_facebook_message(self, message, tags):
|
|
"""
|
|
This allows to create links to the referenced Facebook pages
|
|
|
|
example:
|
|
"Hello Odoo :)" -> "Hello @[542132] Odoo :)"
|
|
"Hello Odoo Social :)" -> "Hello @[542132] Odoo-Social :)"
|
|
"Hello Odoo - Social :)" -> "Hello @[542132] Odoo-Social :)"
|
|
|
|
details:
|
|
"Hello @[42] faketag Odoo - Social :)" -> "Hello @ [42] faketag @[542132] Odoo-Social :)""
|
|
-> With associated tag: {name: "Odoo - Social", offset: 20, length: 13}
|
|
|
|
We take the message until the tag offset: "Hello @[42] faketag "
|
|
and remove the fake tag in the process "Hello @ [42] faketag "
|
|
Then we insert the forged tag "@[542132] Odoo-Social"
|
|
Then take the rest of the message, starting at index tag offset + length, since we inserted the forged tag instead
|
|
|
|
:param message: str message
|
|
:param tags: list of tags in the message as received through Facebook endpoint
|
|
- id: ID of the page/user/group tagged
|
|
- name: name of the page/user/group tagged
|
|
- offset: position of the tag in the text
|
|
- length: length of the tag in the text
|
|
"""
|
|
if not message:
|
|
return message
|
|
|
|
def remove_forged_tags(message):
|
|
"""
|
|
Remove False positive in the message
|
|
(e.g: if someone write "@[42] test" without tagging someone)
|
|
"""
|
|
return re.sub(r'\B@\[', '@ [', message)
|
|
|
|
# Remove Facebook hashtags that have are considered as tags with ID but are not managed by this method
|
|
tags = [tag for tag in tags or [] if not tag.get('name', '').startswith('#')]
|
|
|
|
message_index = 0
|
|
message_with_tags = ''
|
|
for tag in tags or []:
|
|
no_space_name = re.sub(r'\s+', '-', re.sub(r'\s*-\s*', '-', tag['name']))
|
|
forged_tag = '@[%s] %s' % (tag['id'], no_space_name)
|
|
message_with_tags += remove_forged_tags(message[message_index:tag['offset']]) + forged_tag
|
|
message_index = tag['offset'] + tag['length']
|
|
|
|
message_with_tags += remove_forged_tags(message[message_index:])
|
|
|
|
return message_with_tags
|