From Messaging to Community and Short Video: Storage, Recommendations, Mini Apps and Accounts
My previous post argued that a messaging app can keep its data on users’ devices and treat the server as little more than a relay. Community apps and short-video platforms don’t get that option. Their content is public, it arrives as large video files, it has to be ranked for strangers, and the app itself often runs as a mini app inside someone else’s. Below are minimal versions of each, then account security and the relevant regulation.
Public content puts the server back in charge
A messaging app can push data out to devices because only the people in a conversation ever read it. A public post or a short video is written for strangers. The server has to be able to read it to index it, rank it and act on reports, so end-to-end encryption stays with private messages and everything else is an ordinary server-side service.
If you want users to be able to take their data elsewhere, the realistic route today is federation:
- ActivityPub became a W3C Recommendation on 23 January 2018. Every account has an inbox and an outbox, and servers deliver to each other. Mastodon and its relatives are built on it.
- Bluesky opened federation for the AT Protocol on 22 February 2024, so anyone can run their own Personal Data Server, at first with limits on size.
- Threads added a separate fediverse feed and cross-server profile search on 17 June 2025. At the time you couldn’t reply to posts from that feed.
- Matrix replicates rooms across homeservers, and the spec states that no single homeserver controls or owns a room, which suits community chat.
Where the video goes: storage after MinIO
For self-hosted S3-compatible storage, the usual answer for years was MinIO. Its community edition has changed a lot since 2025:
- The release of 24 May 2025 deprecated the built-in admin console in favour of the separate object-browser, and LDAP and OIDC login moved to the commercial AIStor product.
- From October 2025 the README said the community edition would be distributed as source code only, with no pre-built binaries.
- In December 2025 a maintenance-mode notice went into the README: no new changes, security fixes considered case by case.
- GitHub shows the repository archived as read-only on 25 April 2026.
The licence was AGPLv3 throughout, and old releases still run, but the community edition is no longer developed. For a new project, these are the candidates:
| Option | Licence | Language | Positioning and notes |
|---|---|---|---|
| SeaweedFS | Apache 2.0 | Go | Object storage, a file system and Iceberg tables, built for very large numbers of small files |
| Garage | AGPL-3.0 | Rust | Small, geographically spread self-hosted clusters, designed to run outside data centres |
| RustFS | Apache 2.0 | Rust | 1.0 GA on 16 September 2026 and described by its makers as production-ready, though it was only open-sourced in July 2025 |
| Ceph RGW | LGPL | C++ | An S3-compatible gateway on top of Ceph’s RADOS, so you run a whole Ceph cluster with it |
| Apache Ozone | Apache 2.0 | Java | Distributed storage aimed at analytics, with native S3 support |
If you’d rather not run it yourself, Cloudflare’s R2 pricing page states that egress is free, which matters for video, where reads far outnumber writes. Whichever you pick, if your code sticks to the plain S3 API, moving later stays cheap.
The video pipeline itself is roughly: accept the upload, transcode it to several resolutions, cut it into HLS segments (RFC 8216, 2017), store the segments, and serve them through a CDN. For lower latency there’s Low-Latency HLS, which splits segments into smaller partial segments (around 200 ms in Apple’s example). Those rules live in the draft second edition of HLS, draft-pantos-hls-rfc8216bis, and aren’t an RFC yet.
Recommendations: a minimal version with Qdrant
YouTube’s 2016 paper splits recommendation into two stages: one model picks a few hundred candidates from a huge corpus, another ranks them. ByteDance’s 2022 Monolith paper deals with a different problem, training in real time: an embedding table without hash collisions, entries that expire, and a model trained online so that user interactions show up in recommendations almost immediately.
You don’t need any of that to start. Turn each video’s caption and hashtags into a vector, put them in Qdrant, and use the videos someone watched to the end as positive examples and the ones they swiped away as negatives. That’s a working candidate generator:
from importlib.metadata import version
import tempfile
from fastembed import TextEmbedding
from qdrant_client import QdrantClient, models
# Each short video is described by its caption and hashtags.
VIDEOS = {
1: "Pour-over coffee in ten minutes #coffee #howto",
2: "Cold brew that isn't bitter #coffee",
3: "An evening walk with city views on the hill #hiking #citylife",
4: "What to pack for your first high-mountain hike #hiking",
5: "A cat sees snow for the first time #cats #pets",
6: "Choosing an espresso machine for home #coffee #unboxing",
7: "Five mistakes new investors make #finance",
8: "When to set off for a mountain sunrise #hiking #sunrise",
}
MODEL = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
embedder = TextEmbedding(MODEL)
vectors = dict(zip(VIDEOS, embedder.embed(list(VIDEOS.values()))))
liked, skipped = [1, 3], [7] # watched to the end vs swiped away
seen = liked + skipped
with tempfile.TemporaryDirectory() as path:
client = QdrantClient(path=path)
client.create_collection(
"videos",
vectors_config=models.VectorParams(
size=len(vectors[1]), distance=models.Distance.COSINE
),
)
client.upsert(
"videos",
points=[
models.PointStruct(id=i, vector=v.tolist(), payload={"caption": VIDEOS[i]})
for i, v in vectors.items()
],
)
not_seen = models.Filter(must_not=[models.HasIdCondition(has_id=seen)])
for strategy in (
models.RecommendStrategy.AVERAGE_VECTOR,
models.RecommendStrategy.BEST_SCORE,
):
hits = client.query_points(
"videos",
query=models.RecommendQuery(
recommend=models.RecommendInput(
positive=liked, negative=skipped, strategy=strategy
)
),
query_filter=not_seen,
limit=3,
).points
print(strategy.value)
for h in hits:
print(f" {h.score:.3f} {h.payload['caption']}")
client.close()
print("qdrant-client", version("qdrant-client"), "fastembed", version("fastembed"))
Output:
average_vector
0.460 When to set off for a mountain sunrise #hiking #sunrise
0.437 Choosing an espresso machine for home #coffee #unboxing
0.426 Cold brew that isn't bitter #coffee
best_score
0.697 Choosing an espresso machine for home #coffee #unboxing
0.694 Cold brew that isn't bitter #coffee
0.686 When to set off for a mountain sunrise #hiking #sunrise
qdrant-client 1.19.1 fastembed 0.8.1
The cat video stays out, and the picks are split between hiking and coffee. The two strategies score on different scales, so the numbers can’t be compared across them. average_vector folds the examples into a single query vector and costs the same as a normal search. best_score compares against each example separately, so it slows down as examples pile up, but someone who likes two very different things doesn’t get averaged into results that match neither. The first-hike video, which is plainly about hiking, didn’t make the top three under either strategy: vectors built from captions alone are crude, and a real system adds watch time, interactions and popularity.
This version only looks at the content. For “people who watched this also watched that”, Qdrant’s MovieLens example stores each user’s ratings as a sparse vector, finds similar users, then counts what they watched. TikTok’s 2020 explainer covers two more problems you’ll hit straight away:
- Cold start: new users can pick interest categories, and those who skip get a general feed of popular videos.
- Variety: the same post says the feed generally won’t show two videos in a row with the same sound or from the same creator, and sometimes shows things outside a user’s stated interests. Rules like these sit after ranking, separate from the vector search.
Third-party mini apps in a WebView
Platforms that host other developers’ mini apps do it in quite different ways:
- WeChat renders mini programs in a WebView but runs their logic in a separate JsCore thread, with the native client passing messages between the two and making network requests on their behalf.
- Alipay’s documentation says its mini programs don’t run in a browser, so
documentandwindowaren’t available. - LINE MINI Apps opened to applications in Taiwan on 27 September 2023. They’re LIFF web apps running in LINE’s WebView, and from 1 October 2025 every MINI App can also be opened in an ordinary browser.
- Telegram Mini Apps are web pages in a WebView too, and Telegram passes in signed user data at launch.
On iOS, App Store Review Guideline 4.7 also applies. The revision of 25 January 2024 brought mini apps, mini games, chatbots and plug-ins under it and made the host app responsible for them. Under 4.7.2 the host may not expose native APIs to them without Apple’s prior permission, and under 4.7.3 sharing data or privacy permissions needs the user’s explicit consent each time.
Most of the technical trouble comes from two places. The first is the JavaScript bridge. Before Android 4.2 (API 17), addJavascriptInterface let a web page call arbitrary Java methods through reflection (CVE-2012-6636), and Android’s current documentation still warns that the injected object appears in every frame of the WebView, iframes included. It recommends addWebMessageListener with allowedOriginRules, which accepts messages only from the origins you name, and says not to rely on WebView.getUrl() for security decisions. On iOS, WKScriptMessage exposes frameInfo.securityOrigin, so you can check which frame a message came from.
The second is identity. Anything a mini app’s front end knows about the user can be altered before it reaches your back end. LINE’s documentation says plainly not to send the profile from liff.getProfile() to your server; send the ID token instead and have the server verify it with LINE. Telegram signs the launch data: the secret key is an HMAC-SHA256 of the bot token keyed with "WebAppData", and the server recomputes an HMAC over the sorted fields and compares it with the hash field. The example below plays both parts, platform and server, and then tries a tampered payload and an expired one:
import hashlib
import hmac
import json
import time
from urllib.parse import parse_qsl, urlencode
class InitDataError(Exception):
pass
def _check_string(fields: dict[str, str]) -> str:
return "\n".join(f"{k}={v}" for k, v in sorted(fields.items()))
def _secret(bot_token: str) -> bytes:
return hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
def sign(fields: dict[str, str], bot_token: str) -> str:
# What the platform does before handing init data to the mini app.
digest = hmac.new(_secret(bot_token), _check_string(fields).encode(), hashlib.sha256)
return urlencode({**fields, "hash": digest.hexdigest()})
def verify(init_data: str, bot_token: str, max_age: int = 3600) -> dict[str, str]:
# What the mini app's own server must do before trusting anything.
fields = dict(parse_qsl(init_data, strict_parsing=True))
received = fields.pop("hash", "")
expected = hmac.new(
_secret(bot_token), _check_string(fields).encode(), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(received, expected):
raise InitDataError("bad hash")
if time.time() - int(fields["auth_date"]) > max_age:
raise InitDataError("expired")
return fields
BOT_TOKEN = "123456:TEST-ONLY-TOKEN" # dummy value for the example
now = str(int(time.time()))
user = json.dumps({"id": 42, "first_name": "Demo"}, separators=(",", ":"))
good = sign({"auth_date": now, "query_id": "AAE", "user": user}, BOT_TOKEN)
print("valid:", json.loads(verify(good, BOT_TOKEN)["user"])["id"])
forged = good.replace("%22id%22%3A42", "%22id%22%3A1")
old = sign({"auth_date": str(int(now) - 7200), "user": user}, BOT_TOKEN)
for name, data in (("forged", forged), ("old", old)):
try:
verify(data, BOT_TOKEN)
except InitDataError as e:
print(f"{name}: rejected ({e})")
Output:
valid: 42
forged: rejected (bad hash)
old: rejected (expired)
The comparison uses hmac.compare_digest so that timing doesn’t leak how many characters matched, and checking auth_date stops an intercepted payload from being replayed indefinitely. Bot API 8.0, on 17 November 2024, added an Ed25519 signature so that third parties without the bot token can verify the data as well. If you’re the platform, the same idea works for you: sign with a private key and let mini app developers verify with the published public key.
There are numbers on how often keys end up in front-end code. A paper at ACM CCS 2023 scanned 3,450,586 WeChat mini programs and found 40,880 shipping their AppSecret, the master key, in client code. The authors demonstrated account takeover by tampering with phone numbers, abuse of promotions, and theft of paid cloud services.
Is the account system secure enough?
Logging in through LINE, Telegram, Apple or Google settles who the user is. Sessions, permissions and data protection after that are still yours. Two public standards make a reasonable checklist:
- NIST SP 800-63B-4, final since 31 July 2025: passwords used on their own must be at least 15 characters, or 8 when part of multi-factor authentication. Composition rules and forced periodic changes are not allowed, new passwords must be checked against lists of common and breached ones, and at AAL2 at least one phishing-resistant option must be offered. Passkeys count.
- OWASP ASVS 5.0.0, released 30 May 2025: verification requirements for authentication, sessions, access control and more, item by item, which works well as a pre-launch list.
For mini apps specifically, keep the mapping between platform accounts and your own accounts on the server, keyed by a platform user ID you’ve verified, and never write identity fields from the front end straight into the database. Keep session tokens short-lived and ask again before sensitive actions.
Regulation: what has been published in Taiwan and the EU
Taiwan:
- Personal Data Protection Act: the amendment promulgated on 31 May 2023 named a Personal Data Protection Commission as the competent authority and allowed fines on non-government organisations that fail to take security measures without first ordering them to fix the problem, from NT$20,000 to NT$2 million, or NT$150,000 to NT$15 million in serious cases. The amendment promulgated on 11 November 2025 requires non-government organisations to notify affected people and report data breaches to the commission, and gives the commission powers of inspection. Its commencement date is to be set by the Executive Yuan. As of October 2026 the commission’s website still describes it as a preparatory office.
- Fraud Crime Hazard Prevention Act: promulgated on 31 July 2024, covering online advertising platforms, e-commerce, third-party payment and online games. Advertising platforms above a set size must verify advertisers and payers, label ads and AI-generated likenesses, and remove fraudulent ads within a deadline, which the subsidiary rules set at 24 hours. On 16 September 2024 the Ministry of Digital Affairs designated four companies: Google, LY, Meta and TikTok.
- Draft Digital Intermediary Services Act: in September 2022 the National Communications Commission sent the draft back for internal review with no timetable. No revised draft had been published at the time of writing.
- Cyber Security Management Act: the amendment promulgated on 24 September 2025 applies in the private sector only to specified non-government organisations, mainly critical infrastructure providers.
The EU and elsewhere:
- The Digital Services Act has applied in full since 17 February 2024, with extra duties for platforms with 45 million or more average monthly users in the EU. On 5 August 2024 the European Commission accepted TikTok’s commitment to withdraw the TikTok Lite rewards programme from the EU permanently, and on 6 February 2026 it issued preliminary findings that TikTok’s addictive design, including infinite scroll, autoplay, push notifications and its recommender, breaches the DSA.
- On 2 May 2025 Ireland’s Data Protection Commission fined TikTok €530 million under the GDPR over unlawful transfers of European users’ data to China and a lack of transparency.
- Australia’s minimum age of 16 for social media took effect on 10 December 2025, covering Facebook, Instagram, Threads, TikTok, YouTube and others, with fines of up to A$49.5 million.
Notes
- Public content has to be readable by the server, so end-to-end encryption is for private messages. For data portability, look at ActivityPub and the AT Protocol.
- MinIO’s community edition went source-only and into maintenance mode in 2025 and was archived in April 2026. SeaweedFS, Garage, RustFS, Ceph RGW and Apache Ozone are the alternatives, and sticking to the plain S3 API keeps switching cheap.
- A minimal recommender is a Qdrant recommend query with positive and negative examples and a filter for what’s been seen. Variety rules go after ranking.
- Open the mini app bridge only to origins you trust, verify identity on the server from platform-signed tokens or signatures, and keep keys out of front-end code.
- Check the account system against NIST SP 800-63B-4 and OWASP ASVS 5.0.0. In Taiwan, removing fraudulent ads is already law, and breach notification has been enacted but has no commencement date yet.
Further reading
- W3C, ActivityPub: the specification, a Recommendation since 23 January 2018.
- TechCrunch, Bluesky opens up federation and Threads expands open social web integrations: dates and features of each federation step, as reported in the press.
- Matrix, Matrix Specification: how rooms are replicated across homeservers.
- MinIO, GitHub repository and RELEASE.2025-05-24T17-08-30Z: the source-only and maintenance-mode notices in the README, and the archived status.
- It’s FOSS, MinIO moves away from open source: a news timeline of the community edition’s changes.
- Project pages, all self-descriptions: SeaweedFS, Garage, Announcing RustFS 1.0.0 GA, Ceph Object Gateway and Apache Ozone.
- Cloudflare, R2 pricing: the official pricing page, including free egress.
- IETF, RFC 8216: HTTP Live Streaming and the HLS second edition draft, with Apple’s Enabling Low-Latency HLS: the HLS specification and its low-latency extension.
- Covington et al., Deep Neural Networks for YouTube Recommendations: RecSys 2016, the two-stage design.
- Liu et al., Monolith: Real Time Recommendation System With Collisionless Embedding Table: ByteDance, 2022, on real-time training and the embedding table.
- TikTok, How TikTok recommends videos #ForYou: the company’s 2020 explanation of signals, cold start and variety.
- Qdrant, Explore the data and Recommendation system with OVHcloud: the recommend strategies and a collaborative filtering example with sparse vectors, from the official docs.
- WeChat, Framework, and Alipay, Framework overview: how each runtime is split, from the official docs.
- LINE, Using user profile, LINE MINI App 台灣開放申請 (in Chinese) and MINI App browser access: server-side token verification and the rollout dates.
- Telegram, Telegram Mini Apps: launch data validation and the third-party verification added in Bot API 8.0.
- Apple, App Store Review Guidelines and the 25 January 2024 update notice: the text of guideline 4.7.
- Android, Insecure WebView native bridges and Native API access with JavaScript bridge: bridge risks and the
addWebMessageListeneradvice. - NVD, CVE-2012-6636: the
addJavascriptInterfacevulnerability. - Zhang, Yang and Lin, Don’t Leak Your Keys: Understanding, Measuring, and Exploiting the AppSecret Leaks in Mini-Programs: ACM CCS 2023, a peer-reviewed measurement study.
- NIST, SP 800-63B-4, and OWASP, ASVS: the authentication and application security standards.
- Laws & Regulations Database of the Republic of China (Taiwan), Personal Data Protection Act and Fraud Crime Hazard Prevention Act: the texts and amendment history.
- Preparatory Office of the Personal Data Protection Commission, FAQ on the 2025 amendment (in Chinese): breach notification and inspection powers.
- Ministry of Digital Affairs, designation of online advertising platforms (in Chinese): the four companies designated on 16 September 2024.
- Central News Agency, NCC sends the digital intermediary draft back for review (in Chinese): news report on the September 2022 decision.
- Central News Agency, Cyber Security Management Act amendment passes (in Chinese): news report on the 2025 amendment; the text is in the Laws & Regulations Database.
- European Commission, TikTok Lite Rewards and TikTok addictive design: DSA enforcement records.
- Irish Data Protection Commission, TikTok €530 million fine: the decision of 2 May 2025.
- Australian Government, Social media minimum age: the official explanation of the under-16 rule.
Ideas and technical judgement by Sheng; drafted with Claude · examples run on Python 3.13.12, qdrant-client 1.19.1 and fastembed 0.8.1.