cohost

Unofficial Common Lisp client library for Cohost
git clone https://todayiwilllaunchmyinfantsonintoorbit.com/cohost.git
Log | Files | Refs | LICENSE

README.markdown (15998B)


      1 # Cohost for Common Lisp
      2 
      3 ### Decay <decay@todayiwilllaunchmyinfantsonintoorbit.com>
      4 
      5 This is an unofficial API client library for cohost.org, suitable for use in bots or other non-web client software. At present, it is built on top of the reverse-engineered "V1" API used by the Cohost website itself, and is also in early development. In other words, it's super unstable, anything can change at any time, and lots of things may not work. If it breaks, you get to keep both halves. However:
      6 
      7 **This is the canonical documentation and is expected to be accurate. Any functionality that doesn't match the specification here is a bug. I am not hiding any secret knowledge in Discord, Telegram, Matrix or any other cursed chat systems that people try to use as document repos. You can report any bugs or send any pull requests to me via email.**
      8 
      9 ## License
     10 
     11 The STRONGEST PUBLIC LICENSE
     12 
     13 ## Usage
     14 
     15 Fetch this into your ASDF system directory (typically ~/common-lisp/):
     16 
     17 ```
     18 $ cd ~/common-lisp
     19 $ git clone https://todayiwilllaunchmyinfantsonintoorbit.com/cohost.git
     20 ```
     21 
     22 Then in your favorite CL implementation, load it and its dependencies via Quicklisp:
     23 
     24 ```
     25 CL-USER> (ql:quickload 'cohost)
     26 ```
     27 
     28 ### REPL Examples
     29 
     30 Load the library and create a client:
     31 
     32 ```
     33 CL-USER> (ql:quickload 'cohost)
     34 (COHOST)
     35 CL-USER> (defvar *cohost* (cohost:init-client))
     36 *COHOST*
     37 ```
     38 
     39 Login with standard user credentials:
     40 
     41 ```
     42 CL-USER> (cohost:login *cohost* "decay@todayiwilllaunchmyinfantsonintoorbit.com" "hunter2")
     43 "someopaquelogintoken"
     44 ```
     45 
     46 Login with the token returned from an earlier LOGIN:
     47 
     48 ```
     49 CL-USER> (cohost:login-with-token *cohost* "foo")
     50 "someupdatedlogintoken"
     51 ```
     52 
     53 Create a simple post:
     54 
     55 ```
     56 CL-USER> (cohost:simple-post *cohost* "DecayWTF" "Simple Test Post" "This is a *simple* test post!" :tags '("lisp" "bot"))
     57 #<COHOST.CLIENT::CHOST {100391EDA3}>
     58 ```
     59 
     60 Create a CHOST object, construct it with both Markdown and an attachment and post it:
     61 
     62 ```
     63 CL-USER> (let ((post (cohost:new-post *cohost* "DecayWTF" :headline "Test Post"))
     64                (content (cohost:new-markdown-block *cohost* "This is the *markdown* content of my test post!"))
     65                (pic (cohost:new-file-attachment *cohost* #p"~/Pictures/10f.gif")))
     66            (setf (cohost:content-blocks post) (list pic content))
     67            (cohost:post *cohost* post))
     68 #<COHOST.CLIENT::CHOST {1003646653}>
     69 ```
     70 
     71 Get a post by ID:
     72 
     73 ```
     74 CL-USER> (defvar *post-content* (cohost:get-post *cohost* 405148))
     75 *POST-CONTENT*
     76 ```
     77 
     78 Get the current dashboard for the logged-in user's default project:
     79 
     80 ```
     81 CL-USER> (defvar *posts* (cohost:get-dash *cohost*))
     82 *POSTS*
     83 ```
     84 
     85 ## Public API
     86 
     87 The full public API is exported from the COHOST package. Right now nothing is fully stable; in particular,
     88 GET-POST, GET-DASH and other such functions will eventually return CLOS objects instead of more-or-less the raw result of deserializing JSON. The interfaces to post-creation functions (SIMPLE-POST, NEW-POST, NEW-MARKDOWN-BLOCK, NEW-FILE-ATTACHMENT and so forth) that already operate on CLOS objects *can* be expected to be relatively stable, however. Existing class names (eg CHOST, BLOCK, and so on) can also be expected to be stable.
     89 
     90 All boolean values are [generalized booleans](http://clhs.lisp.se/Body/26_glo_g.htm#generalized_boolean) as defined in the X3J13 spec. All non-NIL values are considered true.
     91 
     92 ### Setup/Teardown Functions
     93 
     94 * `(INIT-CLIENT)` - Initializes the client
     95 
     96     **Returns**: A client object to pass to other functions.
     97     
     98 * `(LOGIN CLIENT EMAIL PASSWORD)` - Login with user credentials
     99 
    100     **Parameters**:
    101     
    102     * CLIENT - Initialized client object
    103     * EMAIL - String email address
    104     * PASSWORD - String password
    105     
    106     **Returns**: On success, an opaque string authentication token that can be passed to LOGIN-WITH-TOKEN for later sessions. On failure, NIL.
    107     
    108     **Side Effects**: Logs the CLIENT into Cohost.
    109 
    110     **Notes**: Currently does not distinguish incorrect credentials from any other failure mode. If a token is returned, however, you can be sure that the login was successful and the client is ready for further calls. See LOGIN-WITH-TOKEN for details about the auth token and expirations.
    111     
    112 * `(LOGIN-WITH-TOKEN CLIENT TOKEN)` - Login with authentication token
    113 
    114     **Parameters**:
    115     
    116     * CLIENT - Initialized client object
    117     * TOKEN - An opaque authentication token returned from a previous call to LOGIN.
    118 
    119     **Returns**: On success, an updated authentication token with refreshed expiration. On failure, NIL.
    120 
    121     **Side Effects**: Logs the CLIENT into Cohost.
    122     
    123     **Notes**: Token expiration is hardwired into the token (which is currently just the contents of the "connect.sid" cookie) so it will expire eventually; logins via either LOGIN or LOGIN-WITH-TOKEN can expire at any time, including in the middle of a session. LOGIN-WITH-TOKEN will verify that the token was valid and that the client is correctly logged in; on success, it will return the updated auth token with a new expiration time, so this can also be used to refresh an active login (as can any other Cohost API call at present, since they will all implicitly update the auth cookie).
    124 
    125 ### Post creation/manipulation
    126 
    127 * `(NEW-POST CLIENT PROJECT &KEY ID HEADLINE DRAFT ADULT-CONTENT BLOCKS CONTENT-WARNINGS TAGS SHARE-OF)` - Create a new CHOST object and optionally set parameters or content.
    128 
    129     **Parameters**
    130     
    131     * CLIENT - Initialized client object
    132     * PROJECT - String name of project to post as. Must be a project that the logged-in user has rights to post as.
    133     * ID - The ID of an existing post. This is useful to alter an existing post; if ID is set, POST will edit the specified post ID rather than create a new post. Default NIL.
    134     * HEADLINE - A string specifying the post headline. Default NIL (no headline).
    135     * DRAFT - If true, create the new post as a draft. Default NIL.
    136     * ADULT-CONTENT - If true, marks the post as 18+. Default NIL.
    137     * BLOCKS - A list of CONTENT-BLOCK objects (more information below) specifying the post content. MARKDOWN CONTENT-BLOCKs are posted as sequential paragraphs, while ATTACHMENT CONTENT-BLOCKs (either ATTACHMENT or FILE-ATTACHMENT) post as image attachments at the top of the post, in the order they appear in the list. Default empty (empty post content).
    138     * CONTENT-WARNINGS - A list of strings specifying content warnings to attach to the post. Default empty.
    139     * TAGS - A list of strings specifying post tags. Default empty.
    140     * SHARE-OF - The ID of an existing post. If set, the post will be created as a reply to the specificed post. Default NIL.
    141 
    142     **Returns**: A CHOST object as defined by the parameters.
    143     
    144     **Accessors**:
    145     
    146     * `(PROJECT CHOST)`/`(SETF (PROJECT CHOST) NEW-PROJECT)` - Get or set the PROJECT of CHOST.
    147     * `(ID CHOST)`/`(SETF (ID CHOST) NEW-ID)` - Get or set the ID of CHOST.
    148     * `(HEADLINE CHOST)`/`(SETF (HEADLINE CHOST) NEW-HEADLINE)` - Get or set the HEADLINE of CHOST.
    149     * `(DRAFT CHOST)`/`(SETF (DRAFT CHOST) DRAFTP)` - Get or set the DRAFT state of CHOST.
    150     * `(ADULT-CONTENT CHOST)`/`(SETF (ADULT-CONTENT CHOST) ADULT-CONTENT-P)` - Get or set the ADULT-CONTENT state of CHOST.
    151     * `(CONTENT-BLOCKS CHOST)`/`(SETF (CONTENT-BLOCKS CHOST) NEW-BLOCKS)` - Get or set the CONTENT-BLOCKS list of CHOST.
    152     * `(CONTENT-WARNINGS CHOST)`/`(SETF (CONTENT-WARNINGS CHOST) NEW-CWS)` - Get or set the CONTENT-WARNINGS list of CHOST.
    153     * `(TAGS CHOST)`/`(SETF (TAGS CHOST) NEW-TAGS)` - Get or set the TAGS list of CHOST.
    154     * `(SHARE-OF CHOST)`/`(SETF (SHARE-OF CHOST) SHARE-OF-P)` - Get or set the SHARE-OF post ID of CHOST.
    155 * `(NEW-MARKDOWN-BLOCK CLIENT CONTENT)` - Create a new MARKDOWN CONTENT-BLOCK, defining (part of) the text content of a post.
    156 
    157     **Parameters**
    158     
    159     * CLIENT - Initialized client object
    160     * CONTENT - Markdown content for the new block.
    161     
    162     **Returns**: A MARKDOWN CONTENT-BLOCK object suitable for passing to NEW-POST or (SETF (CONTENT-BLOCKS ...) ...)
    163     
    164     **Accessors**:
    165     
    166     * `(CONTENT MARKDOWN-BLOCK)`/`(SETF (CONTENT MARKDOWN-BLOCK) NEW-CONTENT)` - Get or set the CONTENT of MARKDOWN-BLOCK.
    167 
    168 * `(NEW-FILE-ATTACHMENT CLIENT PATHNAME &KEY ALT-TEXT FILENAME CONTENT-TYPE)` - Create a new FILE-ATTACHMENT CONTENT-BLOCK, defining a file to upload as a post attachment. Currently, only images are supported.
    169 
    170     **Parameters**
    171     
    172     * CLIENT - Initialized client object
    173     * PATHNAME - Pathname of the file to attach.
    174     * ALT-TEXT - String specifying the alt-text for the attachment. Default NIL (no alt-text).
    175     * FILENAME - String specifying the filename to upload the attachment as. If NIL, will use the actual filename (ie, if the pathname is #P"~/Pictures/foo.jpg", it will be uploaded as "foo.jpg". Default NIL.
    176     * CONTENT-TYPE - String specifying the MIME type of the attachment (eg, "image/png" for a PNG file). If NIL, will use TRIVIAL-MIMES to derive the MIME type from the file specification. Default NIL.
    177     
    178     **Returns**: A FILE-ATTACHMENT CONTENT-BLOCK object suitable for passing to NEW-POST or (SETF (CONTENT-BLOCKS ...) ...)
    179     
    180     * `(ALT-TEXT FILE-ATTACHMENT)`/`(SETF (ALT-TEXT FILE-ATTACHMENT) NEW-ALT-TEXT)` - Get or set the ALT-TEXT of FILE-ATTACHMENT.
    181     * `(ATTACHMENT-PATHNAME FILE-ATTACHMENT)`/`(SETF (ATTACHMENT-PATHNAME FILE-ATTACHMENT) NEW-ATTACHMENT-PATHNAME)` - Get or set the PATHNAME of FILE-ATTACHMENT.
    182     * `(ATTACHMENT-FILENAME FILE-ATTACHMENT)`/`(SETF (ATTACHMENT-FILENAME FILE-ATTACHMENT) NEW-ATTACHMENT-FILENAME)` - Get or set the FILENAME of FILE-ATTACHMENT.
    183     * `(ATTACHMENT-CONTENT-TYPE FILE-ATTACHMENT)`/`(SETF (ATTACHMENT-CONTENT-TYPE FILE-ATTACHMENT) NEW-ATTACHMENT-CONTENT-TYPE)` - Get or set the CONTENT-TYPE of FILE-ATTACHMENT.
    184 * `(NEW-ATTACHMENT CLIENT ATTACHMENT-ID &KEY ALT-TEXT)` - Create a new ATTACHMENT CONTENT-BLOCK, defining a BLOCK related to an *existing* and already-uploaded attachment; this is typically not used on post creation but can be useful for editing existing posts.
    185 
    186     **Parameters**
    187     
    188     * CLIENT - Initialized client object
    189     * ATTACHMENT-ID - The ID of the existing attachment.
    190     * ALT-TEXT - String specifying the alt-text for the attachment. Default NIL (no alt-text).
    191     
    192     **Returns**: An ATTACHMENT CONTENT-BLOCK object suitable for passing to NEW-POST or (SETF (CONTENT-BLOCKS ...) ...)
    193     
    194     **Accessors**:
    195     
    196     * `(ATTACHMENT-ID ATTACHMENT)`/`(SETF (ATTACHMENT-ID ATTACHMENT) NEW-ATTACHMENT-ID)` - Get or set the ATTACHMENT-ID of ATTACHMENT.
    197     * `(ALT-TEXT ATTACHMENT)`/`(SETF (ALT-TEXT ATTACHMENT) NEW-ALT-TEXT)` - Get or set the ALT-TEXT of ATTACHMENT.
    198 
    199 * (COPY-POST CLIENT POST) - Create a fresh copy of POST and all BLOCKs.
    200 
    201     **Parameters**
    202     
    203     * CLIENT - Initalized client object
    204     * POST - An existing CHOST object.
    205     
    206     **Returns**: A freshly-consed copy of POST with freshly-consed copies of all BLOCKs. All lists associated with the newly-constructed object are guaranteed not to share structure with the equivalent lists associated with POST (so you can safely say, for instance, (SETF (TAGS NEW-CHOST) (CONS "This is a new tag" (TAGS NEW-CHOST))) without altering POST).
    207 
    208 * `(POST CLIENT POST)` - Post POST to Cohost.
    209 
    210     **Parameters**
    211     
    212     * CLIENT - Initalized client object
    213     * POST - An existing CHOST object.
    214     
    215     **Returns**: `(VALUES NEW-POST POST-RESPONSE)`
    216     
    217     * NEW-POST - A new CHOST object, copied from POST and updated with the new post ID and with any attachments updated with their associated IDs as well.
    218     * POST-RESPONSE - The parsed JSON response from the Cohost API endpoint.
    219 
    220     **Side Effects**: Posts POST to Cohost, with all post settings as specified in POST.
    221     
    222     **Notes**: This does all the heavy lifting for actually posting to Cohost. Does the following:
    223     
    224     * If POST has no ID set, calls the Cohost API to create a new post with the MARKDOWN and ATTACHMENT CONTENT-BLOCKs and configuration specified in POST. If an ID is set, instead updates the live post with that ID (assuming the logged-in user has rights to update that post). If DRAFT is set **or** if there are any FILE-ATTACHMENT BLOCKs in the CONTENT list, it creates or updates the post as draft.
    225     * If there are any FILE-ATTACHMENT blocks, sequentially uploads each one, updates the draft post with the attachment IDs of the newly-created attachments, and collects new ATTACHMENT BLOCKs with the ID set to attach to NEW-POST object in place of the FILE-ATTACHMENTS assciated with POST.
    226     * If there were attachments to upload and DRAFT is not set, updates the live post to unset draft state and post it to the live timeline.
    227     * Creates and returns NEW-POST object with the content and settings from POST, the updated post ID returned by the Cohost API after posting (or the existing ID for post edits), and all FILE-ATTACHMENTs related to POST replaced with ATTACHMENT objects. Note that any BLOCKs other that FILE-ATTACHMENTs are the same objects as those associated with POST, and all other lists (like TAGS) share structure with POST's as well.
    228     
    229     Right now this is extremely brittle and does very little error checking so if something fails (image uploading for instance) it can leave the live post in a broken draft state.
    230     
    231 * `(SIMPLE-POST CLIENT PROJECT HEADLINE MARKDOWN &KEY DRAFT ADULT-CONTENT CONTENT-WARNINGS TAGS SHARE-OF)` - Create and post a simple (Markdown-only) post.
    232 
    233     **Parameters**
    234     
    235     * CLIENT - Logged-in client object
    236     * PROJECT - String name of project to post as. Must be a project that the logged-in user has rights to post as.
    237     * HEADLINE - A string specifying the post headline (or NIL or the empty string for no headline).
    238     * MARKDOWN - A string specifying Markdown content for the post.
    239     * DRAFT - If true, create the new post as a draft. Default NIL.
    240     * ADULT-CONTENT - If true, marks the post as 18+. Default NIL.
    241     * CONTENT-WARNINGS - A list of strings specifying content warnings to attach to the post. Default empty.
    242     * TAGS - A list of strings specifying post tags. Default empty.
    243     * SHARE-OF - The ID of an existing post. If set, the post will be created as a reply to the specificed post. Default NIL.
    244 
    245     **Returns**: A fully-hydrated CHOST object as defined by the parameters, with ID set as returned from the Cohost API.
    246 
    247     **Side Effects**: Posts the defined post to Cohost via the Cohost API, with all post settings as specified.
    248     
    249     **Notes**: This is a wrapper around NEW-POST, NEW-MARKDOWN-BLOCK and POST allowing a simple post with no attachments to be created and posted in one step. All the caveats of POST apply here.
    250     
    251 ### Getters
    252 
    253 * `(GET-POST CLIENT POST-ID)` - Get the content of a single post by ID
    254 
    255     **Parameters**
    256     
    257     * CLIENT - Logged-in client object
    258     * POST-ID - ID of the post to fetch.
    259     
    260     **Returns**: A fully-hydrated CHOST object as described above.
    261     
    262     **Notes**: As the Cohost API does not yet provide a convenient way to fetch a single post with just the ID, this is a two-step process that calls project\_post to get the associated project name and then calls tRPC posts.single\_post, so it requires two round-trips per post.
    263     
    264 * `(GET-DASH CLIENT)` - Get the current front page content for the logged-in user's default project
    265 
    266     **Parameters**
    267     
    268     * CLIENT - Logged-in client object
    269 
    270     **Returns**: A list of fully-hydrated CHOST objects representing the posts present on the user's dash at the time of execution.
    271     
    272     **Notes**: There's no API call for this so it actually peels the JSON output out of a full page load of the cohost dashboard; expect a lot more data transfer than would be normal if this was just a JSON API response (around 60k by my tests).
    273     
    274 ### FAQ
    275 
    276 * Shouldn't you use a proper license?
    277     * No.
    278 * Why isn't this on Github?
    279     * Because I am [not part of your software supply chain](https://iliana.fyi/blog/software-supply-chain/) and Microsoft doesn't get to say that I am.