Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
579 changes: 579 additions & 0 deletions social_media_base/README.rst

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions social_media_base/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import controllers
from . import hooks
from . import models
from . import wizards
53 changes: 53 additions & 0 deletions social_media_base/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Social Media Base",
"summary": "Basic module for social media management.",
"version": "17.0.1.0.0",
"category": "Social Network",
"development_status": "Beta",
"license": "AGPL-3",
"author": "BinhexTeam,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/social",
"maintainers": ["edescalona"],
"depends": ["base", "web", "mail", "utm", "link_tracker"],
# The Enterprise "social" module declares the same social.media, social.account
# and social.post models, so the two cannot live in the same database.
"excludes": ["social"],
"data": [
"security/social_media_base_groups.xml",
"security/ir.model.access.csv",
"security/social_account_security.xml",
"security/social_post_security.xml",
"security/social_post_account_security.xml",
"security/wizard_social_account_security.xml",
"data/utm_medium_data.xml",
"data/ir_cron_data.xml",
"views/social_media_views.xml",
"views/social_account_views.xml",
"views/social_post_views.xml",
"views/social_post_account_views.xml",
"views/utm_campaign_views.xml",
"views/social_action_client_views.xml",
"wizards/wizard_social_account.xml",
"views/social_media_base_menus.xml",
],
"assets": {
"web.assets_backend": [
("include", "web.chartjs_lib"),
"social_media_base/static/src/xml/**/*.xml",
# Partials first: SCSS assets are compiled as a single unit, so
# mixins must be declared before the stylesheets using them.
"social_media_base/static/src/scss/app/_social_mixins.scss",
"social_media_base/static/src/scss/**/*.scss",
"social_media_base/static/src/js/app/**/*.js",
"social_media_base/static/src/js/services/**/*.js",
"social_media_base/static/src/components/**/*.xml",
"social_media_base/static/src/components/**/*.js",
"social_media_base/static/src/components/**/*.scss",
"social_media_base/static/src/js/views/**/*.xml",
"social_media_base/static/src/js/views/**/*.js",
],
},
"installable": True,
}
4 changes: 4 additions & 0 deletions social_media_base/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import thread
39 changes: 39 additions & 0 deletions social_media_base/controllers/thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo.http import request, route

from odoo.addons.mail.controllers.thread import ThreadController
from odoo.addons.mail.models.discuss.mail_guest import add_guest_to_context


class ThreadControllerSocial(ThreadController):
def _prepare_result(self):
return {
"author": {
"id": request.env.user.partner_id.id,
"name": request.env.user.partner_id.name,
"is_company": request.env.user.partner_id.is_company,
"user": {
"id": request.env.uid,
"isInternalUser": request.env.user._is_internal(),
},
"type": "partner",
}
}

@route("/mail/message/post", methods=["POST"], type="json", auth="public")
@add_guest_to_context
def mail_message_post(self, thread_model, thread_id, post_data, context=None):
if thread_model == "social.post.account" and thread_id:
post = request.env[thread_model].browse(int(thread_id)).exists()
if not post:
return None
post.check_access_rights("write")
post.check_access_rule("write")
comment = post.create_comment(post_data, context)
request.env["bus.bus"]._sendone(
request.env.user.partner_id, "comments", comment
)
return self._prepare_result()
return super().mail_message_post(thread_model, thread_id, post_data, context)
57 changes: 57 additions & 0 deletions social_media_base/data/ir_cron_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2025 Binhex <https://www.binhex.cloud>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo noupdate="1">
<record id="send_post_schedule_job" model="ir.cron">
<field name="name">Social Post: Send post schedule</field>
<field name="model_id" ref="model_social_post" />
<field name="state">code</field>
<field name="code">model._run_send_post()</field>
<field name="interval_number">5</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="priority">1</field>
</record>

<record id="webhook_schedule_job" model="ir.cron">
<field name="name">Social: Checking social media updates</field>
<field name="model_id" ref="model_social_account" />
<field name="state">code</field>
<field name="code">model._run_check_media_updates()</field>
<!-- Every run costs API calls against quotas counted per day, so the
interval is what bounds what the check spends when nobody is
using Odoo. Two hours is the delay the notice of new updates may
take to show up on the dashboard. -->
<field name="interval_number">2</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="priority">1</field>
</record>

<record id="initial_sync_account_job" model="ir.cron">
<field name="name">Social: Initial sync of the new accounts</field>
<field name="model_id" ref="model_social_account" />
<field name="state">code</field>
<field name="code">model._run_initial_sync()</field>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field>
<field name="priority">5</field>
</record>

<record id="full_resync_account_job" model="ir.cron">
<field name="name">Social: Full resync of the accounts</field>
<field name="model_id" ref="model_social_account" />
<field name="state">code</field>
<field name="code">model._run_full_resync()</field>
<!-- The only pass that notices what was deleted on the social media,
and the most expensive one: it reads every publication of every
account, one call per page. It runs weekly because the ordinary
refresh covers everything else, and a deletion showing up a few
days late is cheaper than paying for this on every refresh. -->
<field name="interval_number">1</field>
<field name="interval_type">weeks</field>
<field name="numbercall">-1</field>
<field name="priority">10</field>
</record>
</odoo>
13 changes: 13 additions & 0 deletions social_media_base/data/utm_medium_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2026 Binhex <https://www.binhex.cloud>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo noupdate="1">

<!-- Delivery method reported for a social media that declares none of its
own. A connector points its social media at the medium of the social media
instead, `utm` already ships one for the usual ones. -->
<record model="utm.medium" id="utm_medium_social_media">
<field name="name">Social Media</field>
</record>

</odoo>
14 changes: 14 additions & 0 deletions social_media_base/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo.exceptions import UserError


class SocialCredentialsError(UserError):
"""The social media refused the credentials of the account.

Told apart from the other errors because it is the only failure worth
retrying on the spot: refreshing the token may be all it takes for the
same call to go through. Anything the social media refuses about the
content itself is not going to change by trying again.
"""
7 changes: 7 additions & 0 deletions social_media_base/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


def remove_social_media(env, media_type):
"""Remove the data of a social media, called from a connector ``uninstall_hook``."""
env["social.account"]._remove_social_media(media_type)
Loading
Loading