Skip to content

Commit 90c8259

Browse files
authoredDec 28, 2023
Add Admin::Accounts methods (#404)
* 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
1 parent 6960044 commit 90c8259

22 files changed

+1542
-14
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package social.bigbone.rx.admin
2+
3+
import io.reactivex.rxjava3.core.Completable
4+
import io.reactivex.rxjava3.core.Single
5+
import social.bigbone.MastodonClient
6+
import social.bigbone.api.Pageable
7+
import social.bigbone.api.Range
8+
import social.bigbone.api.entity.admin.AccountOrigin
9+
import social.bigbone.api.entity.admin.AccountStatus
10+
import social.bigbone.api.entity.admin.ActionAgainstAccount
11+
import social.bigbone.api.entity.admin.AdminAccount
12+
import social.bigbone.api.method.admin.AdminAccountMethods
13+
14+
/**
15+
* Reactive implementation of [AdminAccountMethods].
16+
*
17+
* Perform moderation actions with accounts.
18+
*
19+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/">Mastodon admin/accounts API methods</a>
20+
*/
21+
class RxAdminAccountMethods(client: MastodonClient) {
22+
23+
private val adminAccountMethods = AdminAccountMethods(client)
24+
25+
/**
26+
* View all accounts, optionally matching certain criteria for filtering, up to 100 at a time.
27+
*
28+
* @param range optional Range for the pageable return value
29+
* @param origin Filter for [AccountOrigin.Local] or [AccountOrigin.Remote]
30+
* @param status Filter for [AccountStatus] accounts
31+
* @param permissions Filter for accounts with <code>staff</code> permissions (users that can manage reports)
32+
* @param roleIds Filter for users with these roles
33+
* @param invitedById Lookup users invited by the account with this ID
34+
* @param username Search for the given username
35+
* @param displayName Search for the given display name
36+
* @param byDomain Filter by the given domain
37+
* @param emailAddress Lookup a user with this email
38+
* @param ipAddress Lookup users with this IP address
39+
*
40+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#v2">Mastodon API documentation: admin/accounts/#v2</a>
41+
*/
42+
@JvmOverloads
43+
fun viewAccounts(
44+
range: Range = Range(),
45+
origin: AccountOrigin? = null,
46+
status: AccountStatus? = null,
47+
permissions: String? = null,
48+
roleIds: List<String>? = null,
49+
invitedById: String? = null,
50+
username: String? = null,
51+
displayName: String? = null,
52+
byDomain: String? = null,
53+
emailAddress: String? = null,
54+
ipAddress: String? = null
55+
): Single<Pageable<AdminAccount>> = Single.fromCallable {
56+
adminAccountMethods.viewAccounts(
57+
range = range,
58+
origin = origin,
59+
status = status,
60+
permissions = permissions,
61+
roleIds = roleIds,
62+
invitedById = invitedById,
63+
username = username,
64+
displayName = displayName,
65+
byDomain = byDomain,
66+
emailAddress = emailAddress,
67+
ipAddress = ipAddress
68+
).execute()
69+
}
70+
71+
/**
72+
* View admin-level information about the given account.
73+
*
74+
* @param withId The ID of the account in the database.
75+
*
76+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#get-one">Mastodon API documentation: admin/accounts/#get-one</a>
77+
*/
78+
fun viewAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
79+
adminAccountMethods.viewAccount(withId = withId).execute()
80+
}
81+
82+
/**
83+
* Approve the given local account if it is currently pending approval.
84+
*
85+
* @param withId The ID of the account in the database.
86+
*
87+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#approve">Mastodon API documentation: admin/accounts/#approve</a>
88+
*/
89+
fun approvePendingAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
90+
adminAccountMethods.approvePendingAccount(withId = withId).execute()
91+
}
92+
93+
/**
94+
* Reject the given local account if it is currently pending approval.
95+
*
96+
* @param withId The ID of the account in the database.
97+
*
98+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#reject">Mastodon API documentation: admin/accounts/#reject</a>
99+
*/
100+
fun rejectPendingAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
101+
adminAccountMethods.rejectPendingAccount(withId = withId).execute()
102+
}
103+
104+
/**
105+
* Permanently delete data for a suspended account.
106+
*
107+
* @param withId The ID of the account in the database.
108+
*
109+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#delete">Mastodon API documentation: admin/accounts/#delete</a>
110+
*/
111+
fun deleteAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
112+
adminAccountMethods.deleteAccount(withId = withId).execute()
113+
}
114+
115+
/**
116+
* Perform an action against an account and log this action in the moderation history.
117+
* Also resolves any open reports against this account.
118+
*
119+
* @param withId The ID of the account in the database.
120+
* @param type The type of action to be taken. One of [ActionAgainstAccount].
121+
* @param text Additional clarification for why this action was taken.
122+
* @param reportId The ID of an associated report that caused this action to be taken.
123+
* @param warningPresetId The ID of a preset warning.
124+
* @param sendEmailNotification Whether an email should be sent to the user with the above information.
125+
*
126+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#action">Mastodon API documentation: admin/accounts/#action</a>
127+
*/
128+
@JvmOverloads
129+
fun performActionAgainstAccount(
130+
withId: String,
131+
type: ActionAgainstAccount,
132+
text: String? = null,
133+
reportId: String? = null,
134+
warningPresetId: String? = null,
135+
sendEmailNotification: Boolean? = null
136+
): Completable {
137+
return Completable.fromAction {
138+
adminAccountMethods.performActionAgainstAccount(
139+
withId = withId,
140+
type = type,
141+
text = text,
142+
reportId = reportId,
143+
warningPresetId = warningPresetId,
144+
sendEmailNotification = sendEmailNotification
145+
)
146+
}
147+
}
148+
149+
/**
150+
* Re-enable a local account whose login is currently disabled.
151+
*
152+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#enable">Mastodon API documentation: admin/accounts/#enable</a>
153+
*/
154+
fun enableDisabledAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
155+
adminAccountMethods.enableDisabledAccount(withId = withId).execute()
156+
}
157+
158+
/**
159+
* Unsilence an account if it is currently silenced.
160+
*
161+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#unsilence">Mastodon API documentation: admin/accounts/#unsilence</a>
162+
*/
163+
fun unsilenceAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
164+
adminAccountMethods.unsilenceAccount(withId = withId).execute()
165+
}
166+
167+
/**
168+
* Unsuspend a currently suspended account.
169+
*
170+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#unsuspend">Mastodon API documentation: admin/accounts/#unsuspend</a>
171+
*/
172+
fun unsuspendAccount(withId: String): Single<AdminAccount> = Single.fromCallable {
173+
adminAccountMethods.unsuspendAccount(withId = withId).execute()
174+
}
175+
176+
/**
177+
* Stops marking an account's posts as sensitive, if it was previously flagged as sensitive.
178+
*
179+
* @see <a href="https://docs.joinmastodon.org/methods/admin/accounts/#unsensitive">Mastodon API documentation: admin/accounts/#unsensitive</a>
180+
*/
181+
fun unmarkAccountAsSensitive(withId: String): Single<AdminAccount> = Single.fromCallable {
182+
adminAccountMethods.unmarkAccountAsSensitive(withId = withId).execute()
183+
}
184+
}

‎bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt

+8
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import social.bigbone.api.method.SuggestionMethods
6565
import social.bigbone.api.method.TagMethods
6666
import social.bigbone.api.method.TimelineMethods
6767
import social.bigbone.api.method.TrendMethods
68+
import social.bigbone.api.method.admin.AdminAccountMethods
6869
import social.bigbone.api.method.admin.AdminCanonicalEmailBlockMethods
6970
import social.bigbone.api.method.admin.AdminDimensionMethods
7071
import social.bigbone.api.method.admin.AdminDomainBlockMethods
@@ -108,6 +109,13 @@ private constructor(
108109
@get:JvmName("accounts")
109110
val accounts: AccountMethods by lazy { AccountMethods(this) }
110111

112+
/**
113+
* Access API methods under the "admin/accounts" endpoint.
114+
*/
115+
@Suppress("unused") // public API
116+
@get:JvmName("adminAccounts")
117+
val adminAccounts: AdminAccountMethods by lazy { AdminAccountMethods(this) }
118+
111119
/**
112120
* Access API methods under the "admin/canonical_email_blocks" endpoint.
113121
*/

‎bigbone/src/main/kotlin/social/bigbone/api/Range.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ class Range @JvmOverloads constructor(
1111
val sinceId: String? = null,
1212
val limit: Int? = null
1313
) {
14-
fun toParameters() = Parameters().apply {
14+
@JvmOverloads
15+
fun toParameters(parameters: Parameters = Parameters()) = parameters.apply {
1516
maxId?.let { append("max_id", maxId) }
1617
minId?.let { append("min_id", minId) }
1718
sinceId?.let { append("since_id", sinceId) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package social.bigbone.api.entity
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
import social.bigbone.DateTimeSerializer
6+
import social.bigbone.PrecisionDateTime
7+
import social.bigbone.api.entity.Role.Permission
8+
9+
/**
10+
* Represents a custom user role that grants permissions.
11+
*
12+
* @see <a href="https://docs.joinmastodon.org/entities/Role/">Mastodon API Role</a>
13+
*/
14+
@Serializable
15+
data class Role(
16+
/**
17+
* The ID of the Role in the database.
18+
*/
19+
@SerialName("id")
20+
val id: Int,
21+
22+
/**
23+
* The name of the role.
24+
*/
25+
@SerialName("name")
26+
val name: String,
27+
28+
/**
29+
* The hex code assigned to this role.
30+
* If no hex code is assigned, the string will be empty.
31+
*/
32+
@SerialName("color")
33+
val color: String,
34+
35+
/**
36+
* A bitmask that represents the sum of all permissions granted to the role.
37+
* Possible values can be found in [Permission].
38+
*
39+
* For convenience, use [getParsedPermissions] to directly get a list of [Permission]s seen in this field.
40+
*/
41+
@SerialName("permissions")
42+
val rawPermissions: Int,
43+
44+
/**
45+
* Whether the role is publicly visible as a badge on user profiles.
46+
*/
47+
@SerialName("highlighted")
48+
val highlighted: Boolean,
49+
50+
/**
51+
* When the role was first assigned.
52+
*/
53+
@SerialName("created_at")
54+
@Serializable(with = DateTimeSerializer::class)
55+
val createdAt: PrecisionDateTime = PrecisionDateTime.InvalidPrecisionDateTime.Unavailable,
56+
57+
/**
58+
* When the role was last updated.
59+
*/
60+
@SerialName("updated_at")
61+
@Serializable(with = DateTimeSerializer::class)
62+
val updatedAt: PrecisionDateTime = PrecisionDateTime.InvalidPrecisionDateTime.Unavailable
63+
) {
64+
65+
/**
66+
* Returns whether this role grants the [Permission] specified in [permission].
67+
* The Mastodon API returns permissions as a bitmask in [rawPermissions].
68+
*
69+
* Example: [rawPermissions] reading 65536 (0b10000000000000000) would return true for [Permission.InviteUsers]
70+
* because its bits match when comparing bitwise.
71+
*/
72+
@Suppress("DataClassContainsFunctions")
73+
fun hasPermission(permission: Permission): Boolean = rawPermissions and permission.bitValue == permission.bitValue
74+
75+
/**
76+
* Gets [rawPermissions] and checks for all available [Permission]s which of them are part of the bitmask.
77+
*
78+
* 458_736 returned by the API.
79+
* In binary, that would be: 1101111111111110000
80+
*
81+
* 1101111111111110000 contains all the following permissions represented by their binary values:
82+
* 10000 --- ManageReports
83+
* 100000 --- ManageFederation
84+
* 1000000 --- ManageSettings
85+
* 10000000 --- ManageBlocks
86+
* 100000000 --- ManageTaxonomies
87+
* 1000000000 --- ManageAppeals
88+
* 10000000000 --- ManageUsers
89+
* 100000000000 --- ManageInvites
90+
* 1000000000000 --- ManageRules
91+
* 10000000000000 --- ManageAnnouncements
92+
* 100000000000000 --- ManageCustomEmojis
93+
* 1000000000000000 --- ManageWebhooks
94+
* 100000000000000000 --- ManageRoles
95+
* 1000000000000000000 --- ManageUserAccess
96+
*
97+
* @return [Permission]s masked in [rawPermissions], i.e. permissions this Role has.
98+
*/
99+
@Suppress("DataClassContainsFunctions")
100+
fun getParsedPermissions(): List<Permission> = Permission.entries.filter(::hasPermission)
101+
102+
/**
103+
* Possible Permission Flag values masked in [rawPermissions].
104+
*
105+
* Use [hasPermission] to check for a specific permission’s availability, or [getParsedPermissions] to get a list
106+
* of [Permission]s.
107+
*/
108+
enum class Permission(val bitValue: Int) {
109+
Administrator(0b1), // 0x1 / 1
110+
DevOps(0b10), // 0x2 / 2
111+
112+
ViewAuditLog(0b100), // 0x4 / 4
113+
ViewDashboard(0b1000), // 0x8 / 8
114+
115+
ManageReports(0b10000), // 0x10 / 16
116+
ManageFederation(0b100000), // 0x20 / 32
117+
ManageSettings(0b1000000), // 0x40 / 64
118+
ManageBlocks(0b10000000), // 0x80 / 128
119+
ManageTaxonomies(0b100000000), // 0x100 / 256
120+
ManageAppeals(0b1000000000), // 0x200 / 512
121+
ManageUsers(0b10000000000), // 0x400 / 1024
122+
ManageInvites(0b100000000000), // 0x800 / 2048
123+
ManageRules(0b1000000000000), // 0x1000 / 4096
124+
ManageAnnouncements(0b10000000000000), // 0x2000 / 8192
125+
ManageCustomEmojis(0b100000000000000), // 0x4000 / 16384
126+
ManageWebhooks(0b1000000000000000), // 0x8000 / 32768
127+
ManageRoles(0b100000000000000000), // 0x20000 / 131072
128+
ManageUserAccess(0b1000000000000000000), // 0x40000 / 262144
129+
130+
InviteUsers(0b10000000000000000), // 0x10000 / 65536
131+
132+
DeleteUserData(0b10000000000000000000) // 0x80000 / 524288
133+
}
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package social.bigbone.api.entity.admin
2+
3+
import social.bigbone.api.method.admin.AdminAccountMethods
4+
import java.util.Locale
5+
6+
/**
7+
* Filter that can be used when viewing all accounts via [AdminAccountMethods.viewAccounts].
8+
* Filters for accounts that are either only local or only remote.
9+
*/
10+
enum class AccountOrigin {
11+
12+
Local,
13+
Remote;
14+
15+
fun apiName(): String = name.lowercase(Locale.ENGLISH)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package social.bigbone.api.entity.admin
2+
3+
import social.bigbone.api.method.admin.AdminAccountMethods
4+
import java.util.Locale
5+
6+
/**
7+
* Filter that can be used when viewing all accounts via [AdminAccountMethods.viewAccounts].
8+
* Filters for accounts that have one of the available status types of this class.
9+
*/
10+
enum class AccountStatus {
11+
12+
Active,
13+
Pending,
14+
Disabled,
15+
Silenced,
16+
Suspended;
17+
18+
fun apiName(): String = name.lowercase(Locale.ENGLISH)
19+
}

0 commit comments

Comments
 (0)
Please sign in to comment.