29 Plugins
Blender Dumbass ( J.Y. Amihud ) edited this page 2025-06-04 21:14:49 +02:00

Plugins are useful to further customize your pages on the site. You could edit the source code of the site directly. But that would make it so you cannot update to new versions, unless of course you want to painstakingly edit each new version of BDServer to have your website as you wish. Or just never update it. In which case you will have to maintain it yourself.

Plugins are a compromise in the middle letting you customize your page, without touching the source of the website.


Note, most things you would want to do are possible by simply giving the server a custom CSS file. No plugins needed.


Making Simple plugin

Plugins folder

To make a plugin, navigate to the folder where your BDServer stores data. It is usually at:

~/.local/share/BDServer

Or you can just run:

python3 run.py --folder

There you will see a folder called plugins . If it's not there, make it.

Plugins files

Plugins are little python scripts with specific functions in them. BDServer will find them alone and will execute them automatically. All you need to do is to save a file with a .py extension into the folder. And have your code written in that file.

So let's make a simple test plugin, like test.py.

Plugin itself

So far only 3 functions are available for plugins. But those 3 functions are plenty to do pretty much anything you want to do with your website.


onHTML(server, html)

This function is used when you want to edit the contents of a page

A simple plugin which adds the words "HELLO WORLD" to every page would look like this:

def onHTML(server, html):
    html = html + "HELLO WORLD"
    return html

In this example, the onHTML function is defined, which will run automatically in BDServer when it will server any html code to the client. You recieve 2 inputs:

  • server which contains a bunch information about the server, current page, and so on and so forth.
  • html which is the html code itself.

And you need to include a return of the html code.

In this case, we take the html code and add to it the words "HELLO WORLD" before sending it.


onGET(server)

This function is useful for custom pages specific just for your website.

Let's make a /helloworld page on your website, which will render a simple page which says HELLO WORLD.

from modules import Render
def onGET(server):
    if server.path == "/helloworld":
        Render.send(server, "HELLO WORLD", 200)
        return True

This function only input is server which can give you a bunch of information, like for example: server.path which is the path somebody is requesting. The function should return either True or False. Depending if the page was sent or not.

BDServer will try to serve a page. But /helloworld is not a page that exists in the server. So it will return a 404 not found page. But before it will do that, it will check to see if any of plugins has implemented anything with /helloworld. If every plugin returns False it will try the next one. And if all of them return False it will show the user the 404 page. But if one of them returns True it knows not to send the user the 404 page.

Notice also that the Render module is being used to actually send the page to the user. In this case a Render.send function specifically. It takes 3 inputs: the server itself. The HTML code you want to send. In this case simple HELLO WORLD. And the HTTP code. For success you send 200. For not found you send 404. And so on and so forth.


onFEDI()

This function runs in a separate thread, every 5 minutes or so. It is useful for maintenance or other reasons.

For example, lets say you want to have a simple plugin that prints out HELLO WORLD every 5 minutes or so.

def onFEDI():
    print("HELLO WORLD")

Notice how there are no inputs or outputs for this plugin. This is equivalent to having a script run separately from the server. But because it is in the server, you can do various interesting things with it. For example blenderdumbass.org uses this function for automatic publishing of links to the website on social media source code.


Adding Metadata to your Plugin.

Due to the server being under the terms of AGPL ( requiring all source that makes the server work published ), there is a handy /plugins page on the site which gives you a list of plugins installed on the site, with an option to download them. Example: https://blenderdumbass.org/plugins

As you can see those plugins have metadata rendering them prettier than just test.py. To add that you need to include a dictionary inside of the plugins code that looks roughly like this:

PluginData = {
    "author":{
        "title":"Blender Dumbass",
        "url":"https://blenderdumbass.org",
        "avatar":"https://blenderdumbass.org/pictures/favicon.png"
    },
    "title":"Test Plugin",
    "description":"Plugin meant for testing. It does nothing.",
    "license":"AGPL-3.0-or-later"}

The important part is the name PluginData which is what BDServer will look for when communicating metadata about the plugins. The author tag could just be a name as one string. But you could also make it a dictionary, with title being a name and url being a URL to which to link when people click on the name. And avatar being the image to use for the avatar.


NOTE: Due to BDServer being under AGPL, all plugins you have on it should be compatible with AGPL. This doesn't mean that they all should be under AGPL. They could be under a simpler license. But the license should not be incompatible with AGPL.


Parsing Inputs

Both onHTML and onGET receive the same server input. And onHTML receives the the HTML code of the page. This section will teach you how to parse those, to get more out of your plugins.


server

The information about the request to the server.

Under the hood server is the BaseHTTPRequestHandler from Python's http.server. So everything available with this datatype is available to get from the server variable. As you could see, I was able to get the path by calling server.path in an earlier example.

List of most important things it gives you:

  • server.path - the path of the request. It will be / for the home page.
  • server.headers - the HTTP headers that are used with the request. It includes things like user agent and cookies.
  • server.parsed - ( added by BDServer ) a list of parsed values from the url. For example to handle /search. If a user is searching for HelloWorld the url in the browser will look like this /search?text=HelloWorld. BDServer parses this to give you clean /search in server.path and a dictionary with a text variable in server.parsed.
  • server.cookie - ( added by BDServer ) a unique identifier of the user. Used primarily for logins.

html

This is the raw HTML code of the page.

The HTML code of the page contains various little comments everywhere which are not rendered in the browser, but are useful for you to make plugins. If you right click on the page to View Page Source you will see various things that start with <!-- and end with -->. This is how you mark comments in HTML. BDServer uses those comments to provide you with various information about the page. And help you parse it, to achieve various effects you might want to achieve.

For example:

<!-- Render.Button --> will denote that the following HTML code was generated by Render.Button function. Or more generally speaking, that the following code describes a button on the page. Then <!-- /Render.Button --> ( notice the little / ) is used to tell you that the code is no longer button. This is similar to how HTML works under the hood too. <button> and </button> would be a way to do it in pure HTML.

With big functions, that render entire pages, there will be a subsection denotation too. For example:

<!-- Render.ArticlePage ( License Box ) --> will be the denotation for the start of the box in the page containing the license of the article. And <!-- Render.ArticlePage ( /License Box ) --> ( noticed the added / ) denotes that the license section is over.


Useful Functions

From within the plugins you can access the rest of the BDServer code-base to use various parts of it to help you make your plugins. For example if you are making a new page with onGET you may want to use buttons that look the same as buttons used in the rest of the server.

Render Module

Render is useful for rendering of most things. If you want the html of a button. You can use:

from modules import Render
button = Render.Button("text", "https:/example.com")

So let's go over the various things this module gives:


Render.Button

image

A button which links to something.

Inputs:

  • text - string: The text to be rendered on the button's surface.
  • link - string: The URL to which the button is linking.
  • icon - string: Icon code ( like: edit which looks like a pencil, or file which looks like a file ). Those are any icons available in the icons folder. But without the .png part.
  • image - string: URL to an image to use instead of an icon. Useful for buttons that reference accounts with custom avatars.
  • rel - string: The text of the rel HTML tag.
  • newpage - boolean: Whether to make the button open the link in the new tab.

Outputs an HTML code such as this:

    <!-- Render.Button -->
    <a class="button" href="/">
        <img alt="[icon ]" src="/pictures/favicon.png" style="height:40px;vertical-align: middle">
    Home Page</a>
    <!-- /Render.Button -->

Render.User

image

Renders a link to a user on the website.

Inputs:

  • username: string: The username of that user. Not the display name.
  • stretch: boolean: Whether to make the link contained, or stretched horizontally.

Outputs an HTML code such as this:

<!-- Render.User --><img alt="[avatar]" style="height:50px;vertical-align: middle" src="/pictures/favicon.png">&nbsp;&nbsp;<a href="/account/blenderdumbass">Blender Dumbass</a></center><!-- /Render.User -->


image

Render the bottom section of the page.

Inputs:

  • server - server object: The server object given to the input of the plugin.

Outputs an HTML code such as this:

    <!-- Render.Footer -->
    <center>

    <!-- Render.Button -->
    <a class="button" href="https://codeberg.org/blenderdumbass/BDServer/">
        <img class="icon" alt="[icon codeberg]" src="/icon/codeberg" style="vertical-align: middle">
    Powered with BDServer</a>
    <!-- /Render.Button -->
    <!-- Render.Button -->
    <a class="button" href="/analytics">
        <img class="icon" alt="[icon analytics]" src="/icon/analytics" style="vertical-align: middle">
    Analytics</a>
    <!-- /Render.Button -->
    </center>
    <!-- /Render.Footer -->

Render.ProgressBar

image

A simple progress bar.

Inputs:

  • frac - float: The fraction of the progress bar, between 0.0 and 1.0.

Outputs an HTML code such as this:

<!-- Render.ProgressBar -->
<div title="32.0%" class="back_progress"><div title="32.0%" class="front_progress", style="width:32.0%"></div></div>
<!-- /Render.ProgressBar -->


Render.head

Head is the <head> part of a page. Which is useful to have for various things. For example, when sharing a link to the page on social media, metadata in the <head> is used by various website to display a pretty preview of the page.

Inputs:

  • title - string: The title of the page.
  • description - string: A short description of the page. Avoid HTML code.
  • image - string: Link to the picture used for previews on social media sites. You can use local links like /pictures/image.jpg if the link is stored on the server.
  • config - dictionary: Specifically the one you get by running Set.Load() ( Set is another BDServer module ). The dictionary contains data about the server's settings.
  • author - a username of an account on the website, who is the author of the page.

Outputs this kind of HTML code:

<head>

    <!-- The head. Part of the HTML code where metadata about the page is storred.
    Including the metadata readable by social media websites, when generating link
    previwews, which are called mata-tags in HTML. -->

    <meta charset="utf-8">

    <title>It's Better to Want It Than to Need It</title> <!-- Title in the browser tab -->
    <link media="all" href="/css" type="text/css" rel="stylesheet" /> <!-- CSS theme link -->
    <link rel="icon" href="/pictures/favicon.png"> <!-- Tiny image in the tab -->

    <!-- Now meta tags for social media -->

    <meta property="og:site_name" content="blenderdumbass . org">
    <meta property="og:title" content="It's Better to Want It Than to Need It">
    <meta property="og:description" content="

A lot of people claim that they need to use proprietary software, either for work or something else. And the question is. Do they consent to it, or the existence of a need makes it some sort of a power dynamic?
">
    <meta property="og:image" content="/pictures/thumbs/want_it_dont_need_it.png">
    
    <!-- RSS Link -->

    <link rel="alternate" title='blenderdumbass . org' type="application/rss+xml" href="/rss" />
    
        <link rel="alternate" title='Just @blenderdumbass' type="application/rss+xml" href="/rss?author=blenderdumbass" />

    
            <meta name=article:author content="Blender Dumbass">
            <meta name=fediverse:creator content="@blenderdumbass@mastodon.online">
        
            <meta http-equiv="onion-location" content="http://ttauyzmy4kbm5yxpujpnahy7uxwnb32hh3dja7uda64vefpkomf3s4yd.onion" />

    <!-- This meta tag is needed for pretty rendering on phones -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    </head>


Render.send

Used to send a page over to the client.

Inputs:

  • server - server object: The server object given to the input of the plugin.
  • html - string: the HTML code of the page to send.
  • code - integer: the HTTP code with which to send the page. ( 200 for success, 404 for not found etc. )

Render.tabs

Gives back a dictionary with information about the various tabs on the website.


Render.accounts

Gives back a dictionary with information about all of the accounts on the website.


Render.validate

Used to validate that a person is logged in. Give back the dictionary with information about the currently logged in user.

Inputs:

  • cookie - string: Specifically server.cookie

Render.rank

Gives the numerical rank of the user.

Inputs:

  • account - dictionary: one of the accounts accessible either with Render.accounts or Render.validate
  • Accounts - dictionary: Optional, the output of the Render.Accounts function. If not provided, the function will run the function again to get the accounts. Used to speed up the execution of the server.

Render.articles

Gives all articles from a specific tab

Inputs:

  • tab - string: A name of a tab.

Render.allArticles

Gives all articles on the server.


Render.suggestedArticles

Gives articles specifically chosen for the user.

Inputs:

  • cookie - string: Unique identifier of the user. Specifically server.cookie.
  • random - boolean: Whether to randomize the articles, or fetch them chronologically.

Render.peerhead

Function to work easier with links from PeerTube. Coverts various links, such as:

https://peer.madiator.cloud/a/blenderdumbass/video-channels
https://peer.madiator.cloud/c/blenderdumbass/videos
https://peer.madiator.cloud/@blenderdumbass

To look the same, as:

blenderdumbass@peer.madiator.cloud

Inputs:

  • peertube - string: Raw link.

Converts PeerTube handles into useful URLs.

Inputs:

  • peertube - string: A peertube handle.

Render.mastohead

Insure something is a mastodon handle.

Inputs:

  • mastodon: string: Either a link to mastodon profile, or the handle.

Convert mastodon handles to useful URLs.

Inputs:

  • mastodon: string: Mastodon handle.

Examples

Let's see a few examples of what you can do with plugins.

Changing background image on one of the articles.

Let's say you want to change the background image, but not everywhere, just for certain articles. Let's say you have an article called "Hello World" to which the background image should be /pictures/helloworld.jpg. The article url is /articles/hello_world.

This is how you could do that with a plugin.

PluginData = {
    "author":{
        "title":"Blender Dumbass",
        "url":"https://blenderdumbass.org",
        "avatar":"https://blenderdumbass.org/pictures/favicon.png"
    },
    "title":"Background Change on Hello World Article",
    "description":"This plugin changes background image on a page /articles/hello_world.",
    "license":"AGPL-3.0-or-later"}

def onHTML(server, html):

    # We want to change only /articles/hello_world
    thearticle = "/article/hello_world"
    if server.path == thearticle:
        
        # We want to generate new CSS for this article.
        newcss =  """
        <style>
        background-image: url("/pictures/hello_world.jpg");
        </style>
        """
        # We want to apply this CSS to the page. After the <body> tag.
        html = html.replace("<body>", "<body>"+newcss)

    # We want to return back the html so it could be sent to the user.
    return html




Moving the title under the thumbnail.

The default way BDServer renders previews of articles is by putting Thumbnail under the Title. But what if you want it to be the other way around?

Here is the code that can flip the position of the Title and Thumbnail:

PluginData = {
    "author":{
        "title":"Blender Dumbass",
        "url":"https://blenderdumbass.org",
        "avatar":"https://blenderdumbass.org/pictures/favicon.png"
    },
    "title":"Move Title After Thumbnail",
    "description":"In article previews, render the title under the thumbnail.",
    "license":"AGPL-3.0-or-later"}

def onHTML(server, html):


    # We want to find where in the page the Render.ArticlePreview is
    start = '<!-- Render.ArticlePreview -->'
    end = '<!-- /Render.ArticlePreview -->'

    # We want to do this change for every instance of Render.ArticlePreview
    for i in range(html.count(start)):

        # Finding the actual part where it is describing in HTML the preview itself.
        box = html[html.find(start)+len(start):html.find(end)]
        boxfixed = box
        
        # Finding the title.
        title = boxfixed[boxfixed.find("<h1>")+4:boxfixed.find("</h1>")]
        
        # Removing the title.
        boxfixed = boxfixed.replace(title, "")
        
        # Inserting it after the thumbnail.
        boxfixed = boxfixed.replace('"></a>', '"></a><br><h1>'+title+'</h1>')
        
        # Swapping the HTML code in the page to the fixed one.
        html = html.replace(box, boxfixed)
        
        # Temporarily removing marks, so that the next preview could be
        # also swapped.
        html = html.replace(start, "STARTDEALTHWITH", 1)
        html = html.replace(end, "ENDDEALTHWITH", 1)
    
    # Restoring the marks back, for any next plugin.
    html = html.replace("STARTDEALTHWITH", start)
    html = html.replace("ENDDEALTHWITH", end)
    
    # And giving back the html so it could be sent to the user.
    return html