Curse Instance Tutorial

Introduction

Welcome to the CurseInstance(CI) tutorial!

This tutorial aims to give you an understanding of CIs, and how to use them efficiently! In the previous tutorial, you should have read about the HandlerCollection, and it’s entry points. You should have noticed some talk about CurseInstances, which will be covered in great detail in this document.

What is a CurseInstance?

A ‘CurseInstance’ is a class that represents CurseForge(CF) information. For example, the CurseAddon class contains info such as the addon name, slug, id, release date, ect.

You can access the name of the addon like so:

# Print the name of the addon
# (Assume 'addon' is a valid CurseAddon instance)

print(addon.name)

CIs also offer convince methods for interacting with said data. These convenience methods automatically make the necessary CurseClient calls with the correct arguments to get the relevant data.

Note

You should have already read the CurseClient tutorial. If you haven’t, you should really check it out, as CurseClient is a very important class!

Why CurseInstances?

CurseInstances are in place to normalize CF data. They ensure that no matter what the cursepy configuration is, your program will always interact with the same classes. If this were not the case, then the end user using cursepy would have to determine what format the raw data being is in, and how to decode it. Because cursepy is modular, this data can be literally anything! So having a standardized way to get this data is very important.

CIs also provide all addon info in a convenient way. Most users do not want to manually parse request data!

Finally, CI’s convenience methods make using cursepy much easier. As stated earlier, these methods take the steps to automatically get extra info. This makes the end user’s life much easier, and allows for a more pythonic, class-based way of interacting with CF.

CI’s use the entry point methods of the CC that returned them. This means that they expect the handlers at the given location to accept the standard arguments, and return the standard objects.

You shouldn’t have to create CI’s yourself, as the handlers you load should do this for you. The only time you should create CI’s are if you are writing a handler! If you ever find yourself in a position where you have to create a CI, then you can pass arguments in the order they are documented, or have a look at the API reference.

Creating CurseInstances

CurseInstances inherit the python dataclass object. This means that many components present in curse instances are implemented by the dataclass module, including the ‘init’ function and instantiation. This means that the init function is not explicitly defined, but is instead generated by the dataclass module. The order of parameters documented is the order that they must be provided to instantiate a dataclass. For example, if the parameters of a dataclass by the name of ‘DummyInstance’ is documented like this:

  • arg1 - First argument

  • arg2 - Second argument

  • arg3 - Third argument

Then you will instantiate the class like so:

inst = DummyInstance(1, 2, 3)

Where ‘1’ will be set to ‘arg1’, ‘2’ will be set to ‘arg2’, and so on.

ALL arguments specified are required. It is highly recommended that you look at the dataclass module documentation, and you should check out the API reference for CurseInstances for more info.

Types of CurseInstances

We will now go through each CI, and lay out there parameters and methods.

More info can be found in the API reference for CIs.

Note

For all coming examples, assume that ‘inst’ is a valid CurseInstance of the type being described.

Before we get into CI types, we will first go over common features every CI has.

Every CI should have attributes which store the raw data and metadata of the request. Raw data is the raw, unprocessed data from the protocol object. This is usually bytes that are encoded in some format. Raw data should really only be touched by people who need it! It is already decoded and put into a format that is accessible (The CI). Note that not all handlers attach this raw data, so be warned!

Metadata is extra data provided by the protocol object. For example, the URLProtocol object provides status codes, request headers, reason strings, ect. about the HTTP connection. This data can be useful if you are interested in connection stats. Again, not all handlers attach this metadata, so be warned!

Here is an example of printing both values:

# Print the raw data:

print(inst.raw)

# Print the meta data:

print(inst.meta)

Extra Functionality

Some CIs have extra functionality that allows them to do extra things, such as write content to an external file, or download data from a remote source to a custom file.

Writer

A CI with writer functionality will allow you to write content to an external file. You can invoke this process by using the ‘write’ method:

inst.write(PATH, append=False)

Where ‘PATH’ is a path like object giving the pathname of the file to be written to. You can also specify if the content is to be appended to the external file by using the ‘append’ parameter.

The CI determines what will be written to the external file. If a CI has this feature, then we will go over what exactly they write in this tutorial.

Downloader

A CI with downloader functionality will allow you to download external content and write it to a file. You can invoke this process by using the ‘download’ method:

inst.download(PATH)

Where ‘PATH’, like in the writer, is a path like object giving the pathname of the file to be written to.

Again, the CI determines what will be downloaded and written to the external file. If a CI has this feature, then we will go over exactly what they download and write.

CurseAttachment

Represents an attachment on CF.

  • title - Title of the attachment

  • id - ID of the attachment

  • thumb_url - URL to the thumbnail of the attachment

    (If the attachment is an image, then the thumbnail is a smaller version of the image)

  • url - URL of the attachment

  • is_thumbnail - Boolean determining if this attachment is a thumbnail of an addon

  • addon_id - ID this addon is apart of

  • description - Description of this attachment

CurseAttachments have the download feature, which means that you can download this attachment using the ‘download’ method:

data = inst.download()

This will download the raw bytes and return them. If you want to write this content to a file, then you can pass a path to the ‘path’ parameter, like so:

written = inst.download('path/to/file.jpg')

Where ‘written’ will be the number of bytes written. If you provide a directory instead of a file to write, then we will automatically use the default name as the file to write to. You can also download the thumbnail using the ‘download_thumbnail’ method, which operates in the same way.

CurseDescription

Represents a description on CF. This description can be any HTMl text!

  • description - Raw HTML description text

  • formatter - Formatter attached to this description.

The stored description is usually in HTML. This may make interpreting and displaying the description difficult. To alleviate this problem, CurseDescription allows for the registration of formatters that can change or alter the text. A formatter is a class that alters the description into something new. You can register valid formatters using the ‘attach_formatter’ method:

inst.attach_formatter(FORM)

Where ‘FORM’ is the formatter to attach. If the formatter is invalid, then we raise a TypeError exception. A formatter is valid if it inherits the ‘BaseFormat’ class.

HandlerCollection objects can automatically attach formatters to CurseDescription objects if specified. You can pass a valid formatter to the ‘default_formatter()’ method on the HC, and the formatter will be attached to every CurseDescription object returned.

Here is a list of all built in formatters:

  • NullFormatter - Does nothing!

  • StripHTML - Strips all HTML elements, leaving(In theory) valid text.

  • BSFormatter - Loads the HTML data into beautiful soup for further parsing.

    This formatter returns a bs4 instance, and beautiful soup MUST be installed, or an exception will be raised!

Here is an example of setting a StripHTML as the default formatter for a HC:

hc.default_formatter(StripHTML())

Where ‘hc’ is a valid HandlerCollection object.

To get the formatted content, you can use the format method:

cont = desc.format()

Where ‘desc’ is a valid CurseDescription object. ‘format’ will send the description thorough the formatter, and return the content the formatter provides.

You can also create your own custom formatters as well. Just inherit the ‘BaseFormatter’ class, and overload the ‘format’ method. The ‘format’ method should return the formatted content.

Here is an example of a custom formatter that appends ‘Super Slick!’ to the end of the description:

# Import BaseFormat:

from cursepy.formatters import BaseFormat

class SuperFormatter(BaseFormat):

    def format(self, data: str) -> str:
        """
        Returns the description, but with
        'Super Slick!' appended to the end.
        """

        return data + 'Super Slick!'

# Attach to a CurseDescription object:

desc.attach_formatter(SuperFormatter())

CurseDescription objects can write content to an external file, as it has writing functionality.

CurseAuthor

Represents an author on CF.

  • id - ID of the author

  • name - Name of the author

  • url - URL to the authors page

CurseAuthor classes is not necessary for CF development, and only acts as extra info if you want it.

CurseGame

Represents a game on CF.

  • name - Name of the game

  • slug - Slug of the game

  • id - ID of the game

  • support_addons - Boolean determining if the game supports addons

  • icon_url - URL to the game icon

  • tile_url - URL to the image used for the game tile

  • cover_url - URL to the image used for the game cover

  • status - Used for determining the game status, defined by constants!

  • api_status - Determining if this game is public or private

Each game has a status, which is defined by the (you guessed it) ‘status’ parameter. You can use these constants to identify the status:

  • [1]: DRAFT - This game is a draft, not meant to be used

  • [2]: TEST - Game is in testing, not meant to be used

  • [3]: PENDING_REVIEW - Game is pending review, not meant to be used

  • [4]: REJECTED - Game has been rejected from the backend, definitely not meant to be used

  • [5]: APPROVED - Game has been approved and is good to be used

  • [6]: LIVE - Game is live and (in theory) being used. This is the best game status!

So, if you wanted to see if a given game is valid and live, then you can check the status:

if game.status == CurseGame.LIVE:
    print('Game is live!')

The ‘api_status’ parameter is used to determine if the game is public or private. You can use these constants to identify the status:

  • [1]: PRIVATE - Game is private and not available for use

  • [2]: PUBLIC - Game is public and available for use

If you want to retrieve the objects that represent the catagories, you can use the ‘categories’ method to retrieve category info like so:

cats = inst.catagories()

This will return a tuple of CurseCategory objects representing each root category.

CurseGame also makes searching addons a breeze. You can use the ‘search’ method to search for addons:

addons = inst.search(SEARCH)

Where SEARCH is a search param. This method will automatically fill in the necessary game ID for you, and will return a tuple of CurseAddon objects. CurseGame also as an ‘iter_search()’ method, which will traverse all pages of the search results.

Note

If you need a primer on searching, check out the CurseClient Tutorial.

CurseCategory

Represents a CurseCategory, and provides methods for getting sub and parent catagories.

  • id - ID of the category

  • game_id - ID of the game the category is associated with

  • name - Name of the category

  • root_id - ID of this objects root category(None if there is no root ID)

  • parent_id - ID of this objects parent category(None if there is no root ID)

  • icon - Icon of the category(CurseAttachment)

  • url - URL to the category page

  • date - Date this category was created

  • slug - Slug of the addon

If you read the intro tutorial (You did read the into tutorial right?), then you will know that catagories can have parent and sub-catagories. CurseCategory objects have methods for traveling though the hierarchy, and each returns CurseCategory objects representing these catagories.

‘sub_categories()’ returns a tuple of CurseCategory objects representing each sub-category, returns an empty tuple if there is no sub-categories.

‘parent_category()’ returns a CurseCategory object representing the parent category, returns None if there is no root category.

‘root_category()’ returns a CurseCategory object representing the root category, returns None if there is no root category.

CurseAddon

Represents an addon on CurseForge.

  • name - Name of the addon

  • slug - Slug of the addon

  • summary - Summary of the addon(Not a full description)

  • url - URL of the addon page

  • lang - Language of the addon

  • date_created - Date this addon was created

  • date_modified - Date this addon was last modified

  • date_release - Date the addons latest release

  • id - ID of this addon

  • download_count - Number of times this addon has been downloaded

  • game_id - ID of the game this addon is in

  • available - Boolean determining if the addon is available

  • experimental - Boolean determining if the addon is experimental

  • authors - Tuple of CurseAuthor instances for this addon

  • attachments - Tuple of CurseAttachments associated with the object

  • category_id - ID of the category this addon is in

  • root_category - ID of the root category this addon is apart of

  • all_categories - Tuple of CurseCategory objects representing all the categories this addon is apart of

  • is_featured - Boolean determining if this addon is featured

  • popularity_score - Float representing this popularity score(Most likely used for ranking)

  • popularity_rank - int representing the addon game’s popularity

  • allow_distribute - If this addon is allowed to be distributed

  • main_file_id - ID of the main file for this addon

  • status - Status of this addon, defined by constants!

  • wiki_url - URL to the addon wiki page

  • issues_url - URL to the addon issues page

  • source_url - URL to the addon source code

To determine the status of the addon, you can use the constants:

  • [1]: NEW - This addon is new, no further progress has been made

  • [2]: CHANGED_REQUIRED - This addon needs to be changed in some way before it is approved

  • [3]: UNDER_SOFT_REVIEW - This addon is under soft review

  • [4]: APPROVED - This addon is approved and ready for use

  • [5]: REJECTED - This addon has been rejected from the backend, definitely not meant to be used

  • [6]: CHANGES_MADE - This addon has been changed since it’s last review

  • [7]: INACTIVE - This addon is inactive and not being maintained

  • [8]: ABANDONED - This addon is abandoned and not being maintained

  • [9]: DELETED - This addon has been deleted

  • [10]: UNDER_REVIEW - This addon is under review

CurseAddon objects do not keep the description info! A special call must be made to retrieve this. CurseAddon offers a property that that can retrieve the description as a CurseDescription object:

desc = inst.description

You can get the files associated with this addon by using the ‘file’ method:

file = inst.file(ID)

Where ID is the ID of the file to retrieve. This method returns a CurseFile object representing the files (We will go over CurseFile objects later in this tutorial!). If you want a list of all files associated with the addon, you can use the ‘files()’ method, which returns a tuple of CurseFile objects.

You can retrieve the CurseGame object representing the game this addon is apart of using the ‘game()’ method. You can also get a CurseCategory object representing the category this addon is apart of by using the ‘category()’ method:

# Get the game:

game = inst.game()

# Get the category:

cat = inst.category()

CurseFile

Represents a file on CF.

  • id - ID of the file

  • addon_id - ID of the addon this file is apart of

  • display_name - Display name of the file

  • file_name - File name of the file

  • date - Date the file was uploaded

  • download_url - Download URL of the file

  • length - Length in bytes of the file

  • version - Version of the game needed to work this file

  • dependencies - Tuple of CurseDependency objects for this file

  • game_id - ID of the game this file is apart of

  • is_available - Boolean determining if the file is available

  • release_type - Release type of the file, defined by constants!

  • file_status - Status of the file, also defined by constants!

  • hashes - Tuple of CurseHash objects representing file hashes

  • download_count - Number of times this file has been downloaded

To determine the release type of the file, you can use the constants:

  • [1]: RELEASE - This file is a release

  • [2]: BETA - This is a beta file

  • [3]: ALPHA - This is an alpha file

To determine the file status, you can use the constants:

  • [1]: PROCESSING - This file is being processed and checked

  • [2]: CHANGES_REQUIRED - This file needs to be changed in some way before it is approved

  • [3]: UNDER_REVIEW - This file is under review

  • [4]: APPROVED - This file is approved and ready for use

  • [5]: REJECTED - This file has been rejected from the backend, definitely not meant to be used

  • [6]: MALWARE_DETECTED - This file has been detected as malware, you really should not use it!

  • [7]: DELETED - This file has been deleted

  • [8]: ARCHIVED - This file has been archived

  • [9]: TESTING - This file is being tested

  • [10]: RELEASED - This file is released and ready to be used

  • [11]: READY_FOR_REVIEW - This file is ready to be reviewed

  • [12]: DEPRECATED - This file has been marked as deprecated

  • [13]: BAKING - This file is being baked (?)

  • [14]: AWAITING_PUBLISHING - This file is awaiting publishing

  • [15]: FAILED_PUBLISHING - This file failed to publish

To determine if a file is good to be used, you can use the ‘good_file()’ method:

if inst.good_file():
    print "This file is good to use!"
else:
    print "This file is not good to use!"

This method simply checks if the file is available, if the file is released, and if the file status is RELEASED. Just because a file is not good does not mean it can’t be used! On top of this, just because a file is good does not mean it will work properly. Our only understanding of the file is what the backend says it is. Production ready files could be poorly made, and non-production ready experimental files could also be valid.

To get the changelog of the file, you can use the ‘changelog’ property:

desc = inst.changelog

This will return a CurseDescription object representing the description. As stated earlier, CI’s use the entry point methods of the HC that returned them. This means that the CurseDescription object will have the default formatter attached to it.

If you want all dependencies for a file, then you can find them under the ‘dependencies’ parameter. You can also get certain dependencies by using the ‘get_dependencies()’ method:

deps = inst.get_dependencies(DEPEN_TYPE)

Where DEPEN_TYPE is a dependency type, defined by the CurseDependency constants. This method will only return dependencies of the specified type.

You can also use the ‘get_addon()’ method to retrieve a CurseAddon object representing the addon this file is attached to.

The CurseFile class also has download functionality. You can use the ‘download()’ method to download this file.

Note

This next section is only relevant to the official CurseForge API!

If you aren’t using the CurseForge backend, then this information (probably) won’t apply to you.

In the official CurseForge API, addons can specify if they are approved for distribution over 3rd party services. Any projects created before the release of the official CurseForge API (around 2020-2021) will have this enabled by default. All new projects created afterwards will have this disabled by default.

If 3rd party distribution is enabled, then the CurseForge API will serve a download URL for each addon file. Otherwise, the CurseForge API will simply server ‘None’, and the ‘download()’ method will fail. You can get around this by using the ‘guess_download()’ method:

url = inst.guess_download()

This method uses some attributes to create a best guess download URL that may work (more testing is required!). This URL will download the addon files from the official CurseForge Content Distribution Network, regardless of the handler that retrieved the data.

It is highly recommended to use a download URL provided by the backend if possible!

Once you have the URL, you can set the ‘download_url’ parameter and then download the file:

inst.download_url = inst.guess_download()
inst.download()

CurseDependency

Represents a file dependency on CF.

  • id - ID of the dependency

  • addon_id - ID of the addon this dependency is apart of

  • file_id - ID of the file this dependency is apart of

  • type - Type of the dependency, defined using constants!

  • required - Boolean determining if the dependency is required

To determine the addon type, you can use the constants:

  • [1]: EMBEDDED_LIBRARY - This dependency is an embedded library

  • [2]: OPTIONAL - This dependency is optional

  • [3]: REQUIRED - This dependency is required

  • [4]: TOOL - This dependency is an optional tool

  • [5]: INCOMPATIBLE - This dependency is incompatible

  • [6]: INCLUDE - This dependency is an include file

To determine if the dependency is required, you can use the ‘required’ parameter. This parameter under the hood is a property which returns ‘True’ if the dependency is required. and ‘False’ if it is not.

To get the CurseAddon and CurseFile objects this dependency is a member of, you can use the ‘addon()’ and ‘file()’ methods respectively.

CurseHash

Represents a file hash on CF.

  • hash - Hash of the file

  • algorithm - Algorithm used to generate the hash, defined using constants!

You can determine the algorithm by using these constants:

  • [1]: SHA1 - SHA1 algorithm

  • [2]: MD5 - MD5 algorithm

These objects can be used to check the integrity of files, and determine if they are valid. Some backends may not provide hashes for certain files, or at all!

Conclusion

That concludes this tutorial on CurseInstance objects! If you have any other questions about the intricacies of CurseInstances, then you should check out the API Reference!