Skip to content

Commit

Permalink
Add Admin::Accounts methods (#404)
Browse files Browse the repository at this point in the history
* Add Admin::Account and Role entities

* Add Admin::Account methods

* Move AccountOrigin, AccountStatus, ActionAgainstAccount to separate file

* Offer parsed permissions in addition to raw permissions from API

* Add explanatory comments for bitwise operations
  • Loading branch information
PattaFeuFeu authored Dec 28, 2023
1 parent 6960044 commit 90c8259
Show file tree
Hide file tree
Showing 22 changed files with 1,542 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package social.bigbone.rx.admin

import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import social.bigbone.MastodonClient
import social.bigbone.api.Pageable
import social.bigbone.api.Range
import social.bigbone.api.entity.admin.AccountOrigin
import social.bigbone.api.entity.admin.AccountStatus
import social.bigbone.api.entity.admin.ActionAgainstAccount
import social.bigbone.api.entity.admin.AdminAccount
import social.bigbone.api.method.admin.AdminAccountMethods

/**
* Reactive implementation of [AdminAccountMethods].
*
* Perform moderation actions with accounts.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/">Mastodon admin/accounts API methods</a>
*/
class RxAdminAccountMethods(client: MastodonClient) {

private val adminAccountMethods = AdminAccountMethods(client)

/**
* View all accounts, optionally matching certain criteria for filtering, up to 100 at a time.
*
* @param range optional Range for the pageable return value
* @param origin Filter for [AccountOrigin.Local] or [AccountOrigin.Remote]
* @param status Filter for [AccountStatus] accounts
* @param permissions Filter for accounts with <code>staff</code> permissions (users that can manage reports)
* @param roleIds Filter for users with these roles
* @param invitedById Lookup users invited by the account with this ID
* @param username Search for the given username
* @param displayName Search for the given display name
* @param byDomain Filter by the given domain
* @param emailAddress Lookup a user with this email
* @param ipAddress Lookup users with this IP address
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#v2">Mastodon API documentation: admin/accounts/#v2</a>
*/
@JvmOverloads
fun viewAccounts(
range: Range = Range(),
origin: AccountOrigin? = null,
status: AccountStatus? = null,
permissions: String? = null,
roleIds: List<String>? = null,
invitedById: String? = null,
username: String? = null,
displayName: String? = null,
byDomain: String? = null,
emailAddress: String? = null,
ipAddress: String? = null
): Single<Pageable<AdminAccount>> = Single.fromCallable {
adminAccountMethods.viewAccounts(
range = range,
origin = origin,
status = status,
permissions = permissions,
roleIds = roleIds,
invitedById = invitedById,
username = username,
displayName = displayName,
byDomain = byDomain,
emailAddress = emailAddress,
ipAddress = ipAddress
).execute()
}

/**
* View admin-level information about the given account.
*
* @param withId The ID of the account in the database.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#get-one">Mastodon API documentation: admin/accounts/#get-one</a>
*/
fun viewAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.viewAccount(withId = withId).execute()
}

/**
* Approve the given local account if it is currently pending approval.
*
* @param withId The ID of the account in the database.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#approve">Mastodon API documentation: admin/accounts/#approve</a>
*/
fun approvePendingAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.approvePendingAccount(withId = withId).execute()
}

/**
* Reject the given local account if it is currently pending approval.
*
* @param withId The ID of the account in the database.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#reject">Mastodon API documentation: admin/accounts/#reject</a>
*/
fun rejectPendingAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.rejectPendingAccount(withId = withId).execute()
}

/**
* Permanently delete data for a suspended account.
*
* @param withId The ID of the account in the database.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#delete">Mastodon API documentation: admin/accounts/#delete</a>
*/
fun deleteAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.deleteAccount(withId = withId).execute()
}

/**
* Perform an action against an account and log this action in the moderation history.
* Also resolves any open reports against this account.
*
* @param withId The ID of the account in the database.
* @param type The type of action to be taken. One of [ActionAgainstAccount].
* @param text Additional clarification for why this action was taken.
* @param reportId The ID of an associated report that caused this action to be taken.
* @param warningPresetId The ID of a preset warning.
* @param sendEmailNotification Whether an email should be sent to the user with the above information.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#action">Mastodon API documentation: admin/accounts/#action</a>
*/
@JvmOverloads
fun performActionAgainstAccount(
withId: String,
type: ActionAgainstAccount,
text: String? = null,
reportId: String? = null,
warningPresetId: String? = null,
sendEmailNotification: Boolean? = null
): Completable {
return Completable.fromAction {
adminAccountMethods.performActionAgainstAccount(
withId = withId,
type = type,
text = text,
reportId = reportId,
warningPresetId = warningPresetId,
sendEmailNotification = sendEmailNotification
)
}
}

/**
* Re-enable a local account whose login is currently disabled.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#enable">Mastodon API documentation: admin/accounts/#enable</a>
*/
fun enableDisabledAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.enableDisabledAccount(withId = withId).execute()
}

/**
* Unsilence an account if it is currently silenced.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#unsilence">Mastodon API documentation: admin/accounts/#unsilence</a>
*/
fun unsilenceAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.unsilenceAccount(withId = withId).execute()
}

/**
* Unsuspend a currently suspended account.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#unsuspend">Mastodon API documentation: admin/accounts/#unsuspend</a>
*/
fun unsuspendAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.unsuspendAccount(withId = withId).execute()
}

/**
* Stops marking an account's posts as sensitive, if it was previously flagged as sensitive.
*
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#unsensitive">Mastodon API documentation: admin/accounts/#unsensitive</a>
*/
fun unmarkAccountAsSensitive(withId: String): Single<AdminAccount> = Single.fromCallable {
adminAccountMethods.unmarkAccountAsSensitive(withId = withId).execute()
}
}
8 changes: 8 additions & 0 deletions bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import social.bigbone.api.method.SuggestionMethods
import social.bigbone.api.method.TagMethods
import social.bigbone.api.method.TimelineMethods
import social.bigbone.api.method.TrendMethods
import social.bigbone.api.method.admin.AdminAccountMethods
import social.bigbone.api.method.admin.AdminCanonicalEmailBlockMethods
import social.bigbone.api.method.admin.AdminDimensionMethods
import social.bigbone.api.method.admin.AdminDomainBlockMethods
Expand Down Expand Up @@ -108,6 +109,13 @@ private constructor(
@get:JvmName("accounts")
val accounts: AccountMethods by lazy { AccountMethods(this) }

/**
* Access API methods under the "admin/accounts" endpoint.
*/
@Suppress("unused") // public API
@get:JvmName("adminAccounts")
val adminAccounts: AdminAccountMethods by lazy { AdminAccountMethods(this) }

/**
* Access API methods under the "admin/canonical_email_blocks" endpoint.
*/
Expand Down
3 changes: 2 additions & 1 deletion bigbone/src/main/kotlin/social/bigbone/api/Range.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class Range @JvmOverloads constructor(
val sinceId: String? = null,
val limit: Int? = null
) {
fun toParameters() = Parameters().apply {
@JvmOverloads
fun toParameters(parameters: Parameters = Parameters()) = parameters.apply {
maxId?.let { append("max_id", maxId) }
minId?.let { append("min_id", minId) }
sinceId?.let { append("since_id", sinceId) }
Expand Down
134 changes: 134 additions & 0 deletions bigbone/src/main/kotlin/social/bigbone/api/entity/Role.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package social.bigbone.api.entity

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import social.bigbone.DateTimeSerializer
import social.bigbone.PrecisionDateTime
import social.bigbone.api.entity.Role.Permission

/**
* Represents a custom user role that grants permissions.
*
* @see <a href="https://docs.joinmastodon.org/entities/Role/">Mastodon API Role</a>
*/
@Serializable
data class Role(
/**
* The ID of the Role in the database.
*/
@SerialName("id")
val id: Int,

/**
* The name of the role.
*/
@SerialName("name")
val name: String,

/**
* The hex code assigned to this role.
* If no hex code is assigned, the string will be empty.
*/
@SerialName("color")
val color: String,

/**
* A bitmask that represents the sum of all permissions granted to the role.
* Possible values can be found in [Permission].
*
* For convenience, use [getParsedPermissions] to directly get a list of [Permission]s seen in this field.
*/
@SerialName("permissions")
val rawPermissions: Int,

/**
* Whether the role is publicly visible as a badge on user profiles.
*/
@SerialName("highlighted")
val highlighted: Boolean,

/**
* When the role was first assigned.
*/
@SerialName("created_at")
@Serializable(with = DateTimeSerializer::class)
val createdAt: PrecisionDateTime = PrecisionDateTime.InvalidPrecisionDateTime.Unavailable,

/**
* When the role was last updated.
*/
@SerialName("updated_at")
@Serializable(with = DateTimeSerializer::class)
val updatedAt: PrecisionDateTime = PrecisionDateTime.InvalidPrecisionDateTime.Unavailable
) {

/**
* Returns whether this role grants the [Permission] specified in [permission].
* The Mastodon API returns permissions as a bitmask in [rawPermissions].
*
* Example: [rawPermissions] reading 65536 (0b10000000000000000) would return true for [Permission.InviteUsers]
* because its bits match when comparing bitwise.
*/
@Suppress("DataClassContainsFunctions")
fun hasPermission(permission: Permission): Boolean = rawPermissions and permission.bitValue == permission.bitValue

/**
* Gets [rawPermissions] and checks for all available [Permission]s which of them are part of the bitmask.
*
* 458_736 returned by the API.
* In binary, that would be: 1101111111111110000
*
* 1101111111111110000 contains all the following permissions represented by their binary values:
* 10000 --- ManageReports
* 100000 --- ManageFederation
* 1000000 --- ManageSettings
* 10000000 --- ManageBlocks
* 100000000 --- ManageTaxonomies
* 1000000000 --- ManageAppeals
* 10000000000 --- ManageUsers
* 100000000000 --- ManageInvites
* 1000000000000 --- ManageRules
* 10000000000000 --- ManageAnnouncements
* 100000000000000 --- ManageCustomEmojis
* 1000000000000000 --- ManageWebhooks
* 100000000000000000 --- ManageRoles
* 1000000000000000000 --- ManageUserAccess
*
* @return [Permission]s masked in [rawPermissions], i.e. permissions this Role has.
*/
@Suppress("DataClassContainsFunctions")
fun getParsedPermissions(): List<Permission> = Permission.entries.filter(::hasPermission)

/**
* Possible Permission Flag values masked in [rawPermissions].
*
* Use [hasPermission] to check for a specific permission’s availability, or [getParsedPermissions] to get a list
* of [Permission]s.
*/
enum class Permission(val bitValue: Int) {
Administrator(0b1), // 0x1 / 1
DevOps(0b10), // 0x2 / 2

ViewAuditLog(0b100), // 0x4 / 4
ViewDashboard(0b1000), // 0x8 / 8

ManageReports(0b10000), // 0x10 / 16
ManageFederation(0b100000), // 0x20 / 32
ManageSettings(0b1000000), // 0x40 / 64
ManageBlocks(0b10000000), // 0x80 / 128
ManageTaxonomies(0b100000000), // 0x100 / 256
ManageAppeals(0b1000000000), // 0x200 / 512
ManageUsers(0b10000000000), // 0x400 / 1024
ManageInvites(0b100000000000), // 0x800 / 2048
ManageRules(0b1000000000000), // 0x1000 / 4096
ManageAnnouncements(0b10000000000000), // 0x2000 / 8192
ManageCustomEmojis(0b100000000000000), // 0x4000 / 16384
ManageWebhooks(0b1000000000000000), // 0x8000 / 32768
ManageRoles(0b100000000000000000), // 0x20000 / 131072
ManageUserAccess(0b1000000000000000000), // 0x40000 / 262144

InviteUsers(0b10000000000000000), // 0x10000 / 65536

DeleteUserData(0b10000000000000000000) // 0x80000 / 524288
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package social.bigbone.api.entity.admin

import social.bigbone.api.method.admin.AdminAccountMethods
import java.util.Locale

/**
* Filter that can be used when viewing all accounts via [AdminAccountMethods.viewAccounts].
* Filters for accounts that are either only local or only remote.
*/
enum class AccountOrigin {

Local,
Remote;

fun apiName(): String = name.lowercase(Locale.ENGLISH)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package social.bigbone.api.entity.admin

import social.bigbone.api.method.admin.AdminAccountMethods
import java.util.Locale

/**
* Filter that can be used when viewing all accounts via [AdminAccountMethods.viewAccounts].
* Filters for accounts that have one of the available status types of this class.
*/
enum class AccountStatus {

Active,
Pending,
Disabled,
Silenced,
Suspended;

fun apiName(): String = name.lowercase(Locale.ENGLISH)
}
Loading

0 comments on commit 90c8259

Please sign in to comment.