Skip to content

Plugins development zh TW

ArchiBot edited this page Apr 6, 2024 · 16 revisions

外掛程式開發

ASF支援可在執行期間載入的自訂外掛程式。 外掛程式使您能夠自訂ASF的行為,例如加入自訂指令、自訂交易邏輯,或與第三方服務或是API進行整體整合。

本頁描述了開發人員視角的ASF外掛程式⸺建立、維護、發布等其他資訊。 若您想從使用者的角度來看,請前往​**這裡**​。


核心

Plugins are standard .NET libraries that define a class inheriting from common IPlugin interface declared in ASF. You can develop plugins entirely independently of mainline ASF and reuse them in current and future ASF versions, as long as internal ASF API remains compatible. Plugin system used in ASF is based on System.Composition, formerly known as Managed Extensibility Framework which allows ASF to discover and load your libraries during runtime.


開始使用

We've prepared ASF-PluginTemplate for you, which you can use as a base for your plugin project. Using the template is not a requirement (as you can do everything from scratch), but we heavily recommend to pick it up as it can drastically kickstart your development and cut on time required to get all things right. Simply check out the README of the template and it'll guide you further. Regardless, we'll cover the basics below in case you wanted to start from scratch, or get to understand better the concepts used in the plugin template - you don't typically need to do any of that if you've decided to use our plugin template.

Your project should be a standard .NET library targetting appropriate framework of your target ASF version, as specified in the compilation section.

The project must reference main ArchiSteamFarm assembly, either its prebuilt ArchiSteamFarm.dll library that you've downloaded as part of the release, or the source project (e.g. if you decided to add ASF tree as a submodule). This will allow you to access and discover ASF structures, methods and properties, especially core IPlugin interface which you'll need to inherit from in the next step. The project must also reference System.Composition.AttributedModel at the minimum, which allows you to [Export] your IPlugin for ASF to use. 除此之外,您可能還希望/需要引用其他公開程式庫,來解譯在某些介面中提供給您的資料結構,但除非您確實需要它們,否則現在這樣就夠了。

If you did everything properly, your csproj will be similar to below:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" Version="8.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="C:\\Path\To\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />

    <!-- If building with downloaded DLL binary, use this instead of <ProjectReference> above -->
    <!-- <Reference Include="ArchiSteamFarm" HintPath="C:\\Path\To\Downloaded\ArchiSteamFarm.dll" /> -->
  </ItemGroup>
</Project>

From the code side, your plugin class must inherit from IPlugin interface (either explicitly, or implicitly by inheriting from more specialized interface, such as IASF) and [Export(typeof(IPlugin))] in order to be recognized by ASF during runtime. 達成這一點最簡單的範例如下:

using System;
using System.Composition;
using System.Threading.Tasks;
using ArchiSteamFarm;
using ArchiSteamFarm.Plugins;

namespace YourNamespace.YourPluginName;

[Export(typeof(IPlugin))]
public sealed class YourPluginName : IPlugin {
	public string Name => nameof(YourPluginName);
	public Version Version => typeof(YourPluginName).Assembly.GetName().Version;

	public Task OnLoaded() {
		ASF.ArchiLogger.LogGenericInfo("Meow");

		return Task.CompletedTask;
	}
}

為了使用您的外掛程式,您必須先編譯它。 您可以使用您的IDE,或在您的專案的根目錄下執行此命令來編譯:

# 若您的專案是獨立的(不需要定義它的名稱,因為它是唯一的)
dotnet publish -c "Release" -o "out"

# 若您的專案屬於ASF的Source Tree的一部份(用以防止編譯不需要的部分)
dotnet publish 您外掛程式的名稱 -c "Release" -o "out"

在這之後,您的外掛程式就已準備好部署。 It's up to you how exactly you want to distribute and publish your plugin, but we recommend creating a zip archive where you'll put your compiled plugin together with its dependencies. This way user will simply need to unpack your zip archive into a standalone subdirectory inside their plugins directory and do nothing else.

這只是讓您入門的最基礎情境。 We have ExamplePlugin project that shows you example interfaces and actions that you can do within your own plugin, including helpful comments. Feel free to take a look if you'd like to learn from a working code, or discover ArchiSteamFarm.Plugins namespace yourself and refer to the included documentation for all available options. We'll also further elaborate on some core concepts below to explain them better.

If instead of example plugin you'd want to learn from real projects, there are several official plugins developed by us, e.g. ItemsMatcher, MobileAuthenticator or SteamTokenDumper. In addition to that, there are also plugins developed by other developers, in our third-party section.


API 可用性

除了您可以在介面中存取的內容之外,ASF還向您公開了許多您可以使用的內部API,以便擴展功能。 舉例來說,若您想向Steam網站傳送某種新請求,您不需從頭開始實作所有內容,特別是處理我們先前已處理過的所有問題。 Simply use our Bot.ArchiWebHandler which already exposes a lot of UrlWithSession() methods for you to use, handling all the lower-level stuff such as authentication, session refresh or web limiting for you. Likewise, for sending web requests outside of Steam platform, you could use standard .NET HttpClient class, but it's much better idea to use Bot.ArchiWebHandler.WebBrowser that is available for you, which once again offers you a helpful hand, for example in regards to retrying failed requests.

We have a very open policy in terms of our API availability, so if you'd like to make use of something that ASF code already includes, simply open an issue and explain in it your planned use case of our ASF's internal API. 只要您的範例情境有意義,我們不太可能會反對。 This also includes all suggestions in regards to new IPlugin interfaces that could make sense to be added in order to extend existing functionality.

Regardless of ASF API availability however, nothing is stopping you from e.g. including Discord.Net library in your application and creating a bridge between your Discord bot and ASF commands, since your plugin can also have dependencies on its own. The possibilities are endless, and we made our best to give you as much freedom and flexibility as possible within your plugin, so there are no artificial limits on anything - your plugin is loaded into the main ASF process and can do anything that is realistically possible to do from within C# code.


API 相容性

需要特別為您強調,ASF是一個使用者應用程式,而非一個您能無條件依賴、具有穩定API介面的程式庫。 This means that you can't assume that your plugin once compiled will keep working with all future ASF releases regardless, it's simply impossible if we want to keep developing the program further, and being unable to adapt to ever-ongoing Steam changes for the sake of backwards compatibility is just not appropriate for our case. 對您來說這應該合乎邏輯,但強調這一點事實很重要。

We'll do our best to keep public parts of ASF working and stable, but we'll not be afraid to break the compatibility if good enough reasons arise, following our deprecation policy in the process. This is especially important in regards to internal ASF structures that are exposed to you as part of ASF infrastructure (e.g. ArchiWebHandler) which could be improved (and therefore rewritten) as part of ASF enhancements in one of the future versions. 我們將會盡最大努力在更新日誌中適當通知您,並在執行期間適時顯示與過時功能相關的警告。 我們不會故意為了重寫而重寫,因此您可以相信,下一個ASF次版更新不會只因為版本號碼增加,而讓您的外掛程式完全失效。但仍最好留意更新日誌,並偶爾驗證一切是否正常運作。


外掛程式相依性

Your plugin will include at least two dependencies by default, ArchiSteamFarm reference for internal API (IPlugin at the minimum), and PackageReference of System.Composition.AttributedModel that is required for being recognized as ASF plugin to begin with ([Export] clause). In addition to that, it may include more dependencies in regards to what you've decided to do in your plugin (e.g. Discord.Net library if you've decided to integrate with Discord).

The output of your build will include your core YourPluginName.dll library, as well as all the dependencies that you've referenced. Since you're developing a plugin to already-working program, you don't have to, and even shouldn't include dependencies that ASF already includes, for example ArchiSteamFarm, SteamKit2 or AngleSharp. 精簡與ASF共用的相依套件並不是外掛程式運作的必要措施,但這樣能使記憶體使用及外掛程式大小顯著減少,同時使效能增加,因為ASF會與您共用它自己的相依套件,並只載入那些它所不知道的程式庫。

在一般情形下,建議是只封裝ASF不包含的,或與ASF包含版本不同/不相容的程式庫。 Examples of those would be obviously YourPluginName.dll, but for example also Discord.Net.dll if you decided to depend on it, as ASF doesn't include it itself. Bundling libraries that are shared with ASF can still make sense if you want to ensure API compatibility (e.g. being sure that AngleSharp which you depend on in your plugin will always be in version X and not the one that ASF ships with), but obviously doing that comes for a price of increased memory/size and worse performance, and therefore should be carefully evaluated.

If you know that the dependency which you need is included in ASF, you can mark it with IncludeAssets="compile" as we showed you in the example csproj above. 這將會告訴編譯器不發布被引用的程式庫,因為ASF已經將它包含在內。 Likewise, notice that we reference the ASF project with ExcludeAssets="all" Private="false" which works in a very similar way - telling the compiler to not produce any ASF files (as the user already has them). This applies only when referencing ASF project, since if you reference a dll library, then you're not producing ASF files as part of your plugin.

If you're confused about above statement and you don't know better, check which dll libraries are included in ASF-generic.zip package and ensure that your plugin includes only those that are not part of it yet. This will be only YourPluginName.dll for the most simple plugins. 若您在執行期間遇到有關某些程式庫的問題,請將那些受影響的程式庫也包含在內。 若一切嘗試都失敗了,您仍可以選擇附隨所有東西。


本機相依性

原生相依套件是作為特定作業系統組建的一部份生成的,因為主機上沒有可用的.NET執行環境,ASF就需要經由自己的.NET執行環境來執行,該環境附隨於特定作業系統組建的一部份。 為了最小化組建的大小,ASF會修整自己的原生相依性,只包含程式中可能用到的程式碼,這樣就會有效減少執行期間未使用到的部分。 This can create a potential problem for you in regards to your plugin, if suddenly you find out yourself in a situation where your plugin depends on some .NET feature that isn't being used in ASF, and therefore OS-specific builds can't execute it properly, usually throwing System.MissingMethodException or System.Reflection.ReflectionTypeLoadException in the process. As your plugin grows in size and becomes more and more complex, sooner or later you'll hit the surface that is not covered by our OS-specific build.

這對於Generic組建版本而言從來都不是問題,因為它們一開始就不會處理原生相依性(因為它們在主機上擁有完整的執行環境來執行ASF)。 This is why it's a recommended practice to use your plugin in generic builds exclusively, but obviously that has its own downside of cutting your plugin from users that are running OS-specific builds of ASF. 若您想知道您遇到的問題是否與原生相依性有關,您也可以使用這個方法來驗證,在ASF Generic組建版本中載入您的外掛程式,並看它是否能夠運作。 如果可以,則說明您的外掛程式已有相依套件,而問題在於原生相依性。

不幸的是,我們不得不做出一個艱難的決定:是將整個執行環境發布成適用於特定作業系統建置的一部分,或是決定刪除其未使用的功能,使得建置檔案比完整版的建置小80 MB以上。 我們選擇了後者,所以您無法讓您的外掛程式直接使用缺少的執行環境功能。 If your project requires access to runtime features that are left out, you have to include full .NET runtime that you depend on, and that means running your plugin in generic ASF flavour. 您無法在適用於特定作業系統的組建中執行您的外掛程式,因為那些組建版本缺少您所需的執行環境功能,且.NET執行環境到目前為止還無法支援「合併」您自行提供的原生相依套件。 或許在未來的某一天會有此功能,但目前而言還無法做到。

ASF的適用於特定作業系統的組建已含有執行我們官方的外掛程式最基礎的附加功能。 除此之外,這也稍微擴充了大多數基礎外掛程式可以使用的額外相依性。 因此,不是所有外掛程式都需要擔心原生相依性⸺只有那些超出ASF與官方外掛程式的外掛程式才需要去擔憂。 這是額外提供的,因為如果我們不論如何都需要為自己的範例加入額外的本機相依性,我們也可以直接將它們搭載到ASF上,使您能夠更輕易地使用它們。 但很遺憾,這並不總是足夠的。隨著您的外掛程式越來越大也越來越複雜,用到被修整的功能的可能性就會增加。 Therefore, we usually recommend you to run your custom plugins in generic ASF flavour exclusively. 您仍然可以手動驗證適用於特定作業系統的ASF組建版本是否具有您的外掛程式功能所需的一切⸺但因為您的外掛程式及我們的ASF都會不斷更新而造成變化,因此維護起來可能會相當困難。

Sometimes it may be possible to "workaround" missing features by either using alternative options or reimplementing them in your plugin. This is however not always possible or viable, especially if the missing feature comes from third-party dependencies that you include as part of your plugin. You can always try to run your plugin in OS-specific build and attempt to make it work, but it might become too much hassle in the long-run, especially if you want from your code to just work, rather than fight with ASF surface.


Automatic updates

ASF offers you two interfaces for implementing automatic updates in your plugin:

  • IGitHubPluginUpdates providing you easy way to implement GitHub-based updates similar to general ASF update mechanism
  • IPluginUpdates providing you lower-level functionality that allows for custom update mechanism, if you require something more complex

The minimum checklist of things that are required for updates to work:

  • You need to declare RepositoryName, which defines where the updates are pulled from.
  • You need to make use of tags and releases provided by GitHub. Tag must be in format parsable to a plugin version, e.g. 1.0.0.0.
  • Version property of the plugin must match tag that it comes from. This means that binary available under tag 1.2.3.4 must present itself as 1.2.3.4.
  • Each tag should have appropriate release available on GitHub with zip file release asset that includes your plugin binary files. The binary files should be available in the root directory within the zip file.

This will allow the mechanism in ASF to:

  • Resolve current Version of your plugin, e.g. 1.0.1.
  • Use GitHub API to fetch latest tag available in RepositoryName repo, e.g. 1.0.2.
  • Determine that 1.0.2 > 1.0.1, check release of 1.0.2 tag to find .zip file with the plugin update.
  • Download that .zip file, extract it, and put its content in the directory that included YourPlugin.dll before.
  • Restart ASF to load your plugin in 1.0.2 version.

Additional notes:

  • Plugin updates require appropriate ASF config values, namely PluginsUpdateMode and/or PluginsUpdateList.
  • Our plugin template already includes everything you need, simply rename the plugin to proper values, and it'll work out of the box.
  • You can use combination of latest release as well as pre-releases, they'll be used as per UpdateChannel the user has defined.
  • There is CanUpdate boolean property you can override for disabling/enabling plugins update on your side, for example if you want to skip updates temporarily, or through any other reason.
  • It's possible to have more than one zip file in the release if you want to target different ASF versions. See GetTargetReleaseAsset() remarks, this allows you to have e.g. MyPlugin-V6.1.zip as well as MyPlugin-V6.2.zip and support both ASF V6.1.x.x as well as V6.2.x.x at once.
  • If the above is not sufficient to you and you require custom logic for picking the correct .zip file, you can override GetTargetReleaseAsset() function and provide the artifact for ASF to use.
  • If your plugin needs to do some extra work before/after update, you can override OnPluginUpdateProceeding() and/or OnPluginUpdateFinished() methods.

This interface allows you to implement custom logic for updates if by any reason IGithubPluginUpdates is not sufficient to you, for example because you have tags that do not parse to versions, or because you're not using GitHub platform at all.

There is a single GetTargetReleaseURL() function for you to override, which expects from you Uri? of target plugin update location. ASF provides you some core variables that relate to the context the function was called with, but other than that, you're responsible to implement everything you need in that function and provide ASF the URL that should be used, or null if the update is not available.

Other than that, it's similar to GitHub updates, where the URL should point to a .zip file with the binary files available in the root directory. You also have OnPluginUpdateProceeding() and OnPluginUpdateFinished() methods available.

Clone this wiki locally