···11+# How to publish a feed
22+33+Supercell is a feed generator, that is to say it is the service that fulfills feed requests and returns the posts that are displayed.
44+55+To invoke Supercell, you first need to create a record on your PDS saying that you are publishing a feed for others to use. This is called feed publishing.
66+77+This is done by creating a record on your PDS with the following structure:
88+99+```json
1010+{
1111+ "$type": "app.bsky.feed.generator",
1212+ "did": "did:web:the_hostname",
1313+ "display_name": "Feed A",
1414+ "description": "A useful feed.",
1515+ "created_at": "2024-10-30T16:15:31Z"
1616+}
1717+```
1818+1919+The `did` is a did:web identifier where the hostname is used to service a DID document that contains a "BskyFeedGenerator" service structure with the hostname to make API calls.
2020+2121+## Publish script
2222+2323+The `publish.py` script can be used to create new feed records or update existing ones.
2424+2525+1. Create a new virtual environment to run the script in: `python -m venv ./venv/`
2626+2. Install the atproto library in the virtual environment: `./venv/bin/pip install atproto`
2727+3. Invoke the script using the virtual environment: `./venv/bin/python ./etc/publish.py --help`
2828+
+40
etc/publish.py
···11+#!/usr/bin/env python3
22+from typing import Optional
33+from atproto import Client, models
44+import argparse
55+66+def main(user: str, password: str, name: str, description: str, server: str, rkey: Optional[str] = None, image: Optional[str] = None):
77+ client = Client()
88+ client.login(user, password)
99+ avatar_blob = None
1010+ if image:
1111+ with open(image, 'rb') as f:
1212+ avatar_data = f.read()
1313+ avatar_blob = client.upload_blob(avatar_data).blob
1414+ response = client.com.atproto.repo.put_record(models.ComAtprotoRepoPutRecord.Data(
1515+ repo=client.me.did,
1616+ collection=models.ids.AppBskyFeedGenerator,
1717+ rkey=rkey,
1818+ record=models.AppBskyFeedGenerator.Record(
1919+ did=f'did:web:{server}',
2020+ display_name=name,
2121+ description=description,
2222+ avatar=avatar_blob,
2323+ created_at=client.get_current_time_iso(),
2424+ )
2525+ ))
2626+ print('Feed URI :', response.uri)
2727+2828+2929+if __name__ == '__main__':
3030+ parser = argparse.ArgumentParser()
3131+ parser.add_argument("-u", "--user", help="The handle to publish the feed under. Ex: smokesignal.events")
3232+ parser.add_argument("-p", "--password", help="The password for the handle publishing the feed")
3333+ parser.add_argument("-n", "--name", help="The name of the feed. Ex: What's Hot")
3434+ parser.add_argument("-d", "--description", help="The description of the feed. Ex: Top trending content from the whole network")
3535+ parser.add_argument("-i", "--image", default=None, help="The path to the avatar image for the feed. Ex: ./path/to/avatar.jpeg")
3636+ parser.add_argument("-s", "--server", help="The server hostname servicing the feed. Ex: feeds.smokesignal.events")
3737+ parser.add_argument("-r", "--rkey", default=None, help="The rkey of a feed being updated.")
3838+ args = parser.parse_args()
3939+ main(args.user, args.password, args.name, args.description, args.server, args.rkey, args.image)
4040+