Odoo Community Association

Social Media Sync

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

This module brings back into Odoo what an account already published on its social media: the posts themselves, the figures each of them collected, and their comments and reactions.

It is separate from Social Media Base because of what it costs. Base asks the social media for a fixed number of things per account — publish, delete, the daily series of the page, the figures of the publications of the last 30 days — and that number does not change whether the account published once or ten thousand times. Everything whose cost grows with the history of the account lives here: one call per page of posts, one call per publication to check it is still there, one call per comment thread. An installation that only writes and publishes does not have to pay for any of it.

Social Media Base never depends on this module nor calls into it. Where base needs something only the synchronization knows how to do, it declares an empty hook and carries on, so base works installed alone.

Main features:

This module implements the API of no social media in particular: it brings the scheduled actions, the frontend and the common interface, and a synchronization connector is what implements the calls for one social media. The only thing it fetches on its own is the media of an imported publication, from the URL the connector hands it.

Table of contents

Installation

This module depends on Social Media Base and is not installed with it: an Odoo that only writes and publishes does not need it, and the whole point of having it apart is not paying for what it does.

It brings nothing of its own for a social media in particular. A synchronization connector is what implements the calls for one of them, the same way Social Media Linkedin or Social Media X implement publishing. Installed without one, the scheduled actions run, find nothing to ask anybody, and leave everything as it is.

Configuration

Nothing has to be configured for this module to work: it uses the accounts, the credentials and the groups of Social Media Base.

What is worth reviewing is the two scheduled actions it adds, in Settings / Technical / Automation / Scheduled Actions:

  • Social: Initial sync of the new accounts runs monthly, and is also triggered on the spot every time an account is linked. It only picks up the accounts still waiting for that first import, so the monthly run is a safety net rather than the normal path.
  • Social: Full resync of the accounts runs weekly. It is the only pass that notices a publication deleted on the social media, and the most expensive one: it reads every publication of every account, one call per page. Making it run more often is what turns a deletion noticed a few days late into a quota problem.

Both intervals are the ones to move if the social media of an account is strict about quotas.

The retention of the downloaded medias is one system parameter, in Settings / Technical / Parameters / System Parameters, which only an administrator reaches: social_media_sync.media_max_age_days. The module installs it at 0, so it is there to be found, and zero is no policy at all: no media is ever released.

A system parameter holds text, and this one is read as a number of days. A value that cannot be read as one — a word — is taken as no policy and leaves a warning in the log; an empty value, zero and any negative number are no policy too and are not worth a warning. No media is released in any of those cases.

Written as a positive number of days, the daily vacuum releases the images and videos this module downloaded for the imported publications older than that, and the files are deleted a day later. One run reaches a thousand of the aged publications that still hold medias. A publication already released leaves the next run instead of taking the place of another one, so a database holding more aged publications than that is released in full over the following runs. Two things to weigh before writing a number:

  • What is lost is the media itself. The card of an aged publication is drawn with no image and no placeholder in its place. The link to the social media, where the media still is, stays.
  • What is kept is what each social media made of that media. The next synchronization pass knows the publication already had it and does not ask for it again, so the policy frees the disk once instead of paying for the same bytes every week.

Only the publications imported from a social media are reached. The medias of a post published from Odoo are editorial content and are never aged out, whatever the age of the post.

The size of each downloaded media is capped by a second system parameter, in the same place: social_media_sync.media_max_size_mb. The module installs it at 100, in megabytes, and an update of the module does not overwrite what an administrator wrote in it. A media is held whole in memory while the import runs, and the cap is what keeps one large video from exhausting it.

A media larger than the cap is not downloaded. When the social media announces the size of the file, the download stops before reading it; when it does not, it stops as soon as what was read goes past the cap. The publication is imported without that media, and a warning in the log names the media, its size and this parameter. As the publication does not hold that media, every synchronization pass asks for it again and stops at the cap again, until the parameter is raised above its size. A long video can well be larger than 100 megabytes, so an account publishing them is the one to raise it for.

Zero is no cap at all, and so are an empty value, any negative number and a parameter that was deleted: every media is downloaded whatever its size, and nothing is logged. A value that cannot be read as a whole number of megabytes — a word, a decimal — does not remove the cap: it is taken as 100 and leaves a warning in the log.

Usage

Importing what an account already published.

  • Right after an account is linked, its publications and their statistics are imported: the scheduled action Social: Initial sync of the new accounts is triggered on the spot and the dashboard shows the account as syncing until it is over.
  • If that first import fails, the account stops waiting for it and is not retried on its own: press the Update button of the dashboard to import it again. The reason is left in the chatter of the account, because the scheduled action runs with nobody connected to be notified.
  • If the import loses a race against another update of the same account, it keeps the account waiting and asks the scheduled action to come back a few minutes later. Unlike a web request, a scheduled action gets no retry of its own.
  • If the social media does not let the import run —the requests of the plan are spent, and the account gets a notice saying so— nothing is brought in and nothing is recorded as a failure: the account keeps waiting and the scheduled action is asked to come back a few minutes later. The Update button of the dashboard behaves the same way, and only stops announcing the first import once the social media was really read.
  • Publications created outside of Odoo are the only ones whose medias the import has to download, and so the only ones whose images appear on the dashboard after it rather than before: a publication sent from Odoo already shares the medias of its post.
  • The time series of the account is filled backwards when the account is linked, by Social Media Base, as far back as the social media answers by day; the first import only asks for it again when that fill could not be read then. How far back that is belongs to the social media, not to Odoo, so two accounts may well start with a different depth of history.
  • Afterwards, the Update button of the dashboard imports again on demand. Without this module that button refreshes the daily series of the account and the figures of the publications of the last 30 days; with it, the same press also imports the publications.
  • Pressed with no account picked, the button imports only the accounts known to be behind: the ones announcing publications to import and the ones whose first import never ran. Pressed on a single account, it imports that account whatever it announces. The figures of every account are refreshed either way, because they cost a fixed number of calls and they move without anything being published; it is the import whose cost grows with the history of the account.
  • When nothing needed importing, the button says so —The data was updated. No new publications.— instead of announcing publications it did not bring in.
  • The figures imported for a publication — impressions, social media clicks, shares, likes, comments, interactions and engagement — are added by this module to the list of publications and to their form, which span the whole history. The Statistics dialog of a card belongs to Social Media Base, which reads those figures back for the publications of the last 30 days on its own; without this module the list and the form show only the tracked clicks, counted by the link tracker.
  • The totals a post adds up from its publications — likes, comments, clicks, shares, interactions and engagement — are drawn by this module on the list of posts, and Clicks, Interactions and Engagement on the kanban card of a post; without it those columns and that card carry a zero nothing can turn into a number.

What each notice on a card announces.

Three different things can be pending on an account, and each one has its own notice and its own way out. They are independent: an account may be carrying one, two or the three of them at once, and none of them says anything about the others.

  • The credentials expired. The warning of Social Media Base. Only a new authorization takes it down, and the Update button does not.
  • The first import is running. Drawn by this module while the scheduled action brings in what the account had already published.
  • There are publications to import. Added by this module when the check for updates finds that the account moved on the social media. The Update button is what resolves it, and the notice goes away on its own once the import is over, without the page being reloaded.

Only a social media whose API can tell that an account moved without reading its publications raises the third one. Where it cannot —reading the timeline is the import— nothing is announced between two runs, and those accounts are imported on every pass instead.

Noticing what was deleted on the social media.

  • The ordinary import asks the social media only about what it needs, which is what keeps it affordable on an account with thousands of publications. What it cannot notice that way is that a publication was deleted on the social media: nothing is left to ask about.
  • The scheduled action Social: Full resync of the accounts reads everything again once a week and reconciles it, and each connector may also offer to run it on demand from the account form. A publication deleted on the social media may therefore take up to a week to be reported as such.
  • A publication found gone is marked as Deleted and keeps its reference on the social media: detection is not infallible, so a line wrongly marked can be recognised and restored by the next full pass.
  • Opening a publication already asks the social media without this module, so a deletion the user runs into is marked right away either way. What this module adds is noticing the ones nobody opens.

Comments and reactions.

  • Commenting a publication from the dashboard publishes the comment on the social media, under the account of the publication, which is what the composer announces.
  • Answering a comment moves the composer under it, so the reply is written where it will be read. Once the reply is published the composer returns to the head of the dialog; only a reply the social media rejected keeps the aim, so the retry starts under the same comment. Pressing the entry again hands the composer back to the head of the dialog, which also holds it while the answered comment is not on the list.
  • A comment is answered where the social media serves the replies. Where the whole thread already arrives with the comments, the replies are nested from what is already on screen and nothing else is asked for.
  • Recommend is offered both on the publication and on each of its comments, and is sent under the same account. It is only shown where the social media supports it on comments: a connector that recommends them declares itself, and the entry is not rendered for the publications of the ones that do not.
  • The entry is a toggle: a social media holds one reaction per account, so it draws what the account already recommended and pressing it there withdraws the reaction. A social media that could not be read leaves the entry as it was drawn instead of claiming one thing or the other.
  • A reaction or a comment that fails with a not found does not mark the publication as deleted on its own: the publication is asked about first, because a social media answers the same for a reference it does not recognise and for a lost permission.

Known issues / Roadmap

actor_urn

social.post.account.actor_urn holds who the social media says published a publication: the organization page on LinkedIn, the author of the tweet on X. The import is what fills it, and no view shows it, so nothing reads it back yet. It looks like it belongs to the reactions, which take the actor performing them as an argument, and it is the kind of field the family either starts using or drops.

Storage of the imported medias

The import downloads the medias of every publication it brings in and stores them as ordinary ir.attachment records, so the filestore grows with the history of the accounts and not with what is published from Odoo: an account importing years of publications brings in years of images.

An imported publication has no post to share attachments with, so nothing is shared here: one image of one publication is one attachment, and the same image published on two accounts is imported twice. media_refs does not change that, and is not there for it: it is what tells the next synchronization which references this publication already holds, so that a media is downloaded once and not on every pass. The bytes of two identical images still land on the same file, because the filestore keys its files by the hash of their content; what multiplies is the rows.

What ages them out is one number for the whole database. social_media_sync.media_max_age_days reaches every imported publication older than it, whatever its account, so there is no way to keep the medias of one account and age out those of another, and a publication kept only for its figures still costs its images until that age is reached. The deletion itself belongs to the vacuum: _gc_aged_post_medias releases what the policy reaches, the next synchronization releases what the social media no longer serves, and _gc_lost_media_attachments deletes both a day later. Serving those bytes from somewhere else is configured at the level of Odoo, through ir_attachment.location, not from here.

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed feedback.

Do not contact contributors directly about support or help with technical issues.

Credits

Authors

  • Binhex

Contributors

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

Current maintainer:

edescalona

This module is part of the OCA/social project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.