mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-06-16 02:19:08 +00:00
flowtyped everything, some functional, safety, and structural changes
This commit is contained in:
parent
6f3eca7a64
commit
d2aecb38ca
92 changed files with 17554 additions and 1440 deletions
|
@ -1,10 +1,12 @@
|
|||
const Logger = require('../logger')
|
||||
// @flow
|
||||
import { Logger } from '../logger'
|
||||
import { type AppContext } from '../Roleypoly'
|
||||
|
||||
class Service {
|
||||
constructor (ctx) {
|
||||
export default class Service {
|
||||
ctx: AppContext
|
||||
log: Logger
|
||||
constructor (ctx: AppContext) {
|
||||
this.ctx = ctx
|
||||
this.log = new Logger(this.constructor.name)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Service
|
||||
|
|
|
@ -1,37 +1,71 @@
|
|||
const Service = require('./Service')
|
||||
const discord = require('discord.js')
|
||||
const superagent = require('superagent')
|
||||
// @flow
|
||||
import Service from './Service'
|
||||
import superagent from 'superagent'
|
||||
// import invariant from 'invariant'
|
||||
import { type AppContext } from '../Roleypoly'
|
||||
import {
|
||||
type Message,
|
||||
type Guild,
|
||||
type Role,
|
||||
type GuildMember,
|
||||
type Collection,
|
||||
Client
|
||||
} from 'discord.js'
|
||||
|
||||
export type UserPartial = {
|
||||
id: string,
|
||||
username: string,
|
||||
discriminator: string,
|
||||
avatar: string
|
||||
}
|
||||
|
||||
export type Permissions = {
|
||||
isAdmin: boolean,
|
||||
canManageRoles: boolean
|
||||
}
|
||||
|
||||
type ChatCommandHandler = (message: Message, matches: string[], reply: (text: string) => Promise<Message>) => Promise<void>|void
|
||||
type ChatCommand = {
|
||||
regex: RegExp,
|
||||
handler: ChatCommandHandler
|
||||
}
|
||||
|
||||
class DiscordService extends Service {
|
||||
constructor (ctx) {
|
||||
super(ctx)
|
||||
botToken: string = process.env.DISCORD_BOT_TOKEN || ''
|
||||
clientId: string = process.env.DISCORD_CLIENT_ID || ''
|
||||
clientSecret: string = process.env.DISCORD_CLIENT_SECRET || ''
|
||||
oauthCallback: string = process.env.OAUTH_AUTH_CALLBACK || ''
|
||||
isBot: boolean = process.env.IS_BOT === 'true'
|
||||
|
||||
this.botToken = process.env.DISCORD_BOT_TOKEN
|
||||
this.clientId = process.env.DISCORD_CLIENT_ID
|
||||
this.clientSecret = process.env.DISCORD_CLIENT_SECRET
|
||||
this.oauthCallback = process.env.OAUTH_AUTH_CALLBACK
|
||||
this.botCallback = `${ctx.config.appUrl}/api/oauth/bot/callback`
|
||||
this.appUrl = process.env.APP_URL
|
||||
this.isBot = process.env.IS_BOT === 'true' || false
|
||||
appUrl: string
|
||||
botCallback: string
|
||||
rootUsers: Set<string>
|
||||
client: Client
|
||||
cmds: ChatCommand[]
|
||||
constructor (ctx: AppContext) {
|
||||
super(ctx)
|
||||
this.appUrl = ctx.config.appUrl
|
||||
|
||||
this.botCallback = `${this.appUrl}/api/oauth/bot/callback`
|
||||
this.rootUsers = new Set((process.env.ROOT_USERS || '').split(','))
|
||||
|
||||
this.client = new discord.Client()
|
||||
this.client = new Client()
|
||||
this.client.options.disableEveryone = true
|
||||
|
||||
this.cmds = this._cmds()
|
||||
this._cmds()
|
||||
|
||||
this.startBot()
|
||||
}
|
||||
|
||||
ownGm (server) {
|
||||
ownGm (server: string) {
|
||||
return this.gm(server, this.client.user.id)
|
||||
}
|
||||
|
||||
fakeGm ({ id = 0, nickname = '[none]', displayHexColor = '#ffffff' }) {
|
||||
fakeGm ({ id = '0', nickname = '[none]', displayHexColor = '#ffffff' }: $Shape<{ id: string, nickname: string, displayHexColor: string }>) { // eslint-disable-line no-undef
|
||||
return { id, nickname, displayHexColor, __faked: true, roles: { has () { return false } } }
|
||||
}
|
||||
|
||||
isRoot (id) {
|
||||
isRoot (id: string): boolean {
|
||||
return this.rootUsers.has(id)
|
||||
}
|
||||
|
||||
|
@ -50,19 +84,27 @@ class DiscordService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
getRelevantServers (userId) {
|
||||
getRelevantServers (userId: string): Collection<string, Guild> {
|
||||
return this.client.guilds.filter((g) => g.members.has(userId))
|
||||
}
|
||||
|
||||
gm (serverId, userId) {
|
||||
return this.client.guilds.get(serverId).members.get(userId)
|
||||
gm (serverId: string, userId: string): ?GuildMember {
|
||||
const s = this.client.guilds.get(serverId)
|
||||
if (s == null) {
|
||||
return null
|
||||
}
|
||||
return s.members.get(userId)
|
||||
}
|
||||
|
||||
getRoles (server) {
|
||||
return this.client.guilds.get(server).roles
|
||||
getRoles (server: string) {
|
||||
const s = this.client.guilds.get(server)
|
||||
if (s == null) {
|
||||
return null
|
||||
}
|
||||
return s.roles
|
||||
}
|
||||
|
||||
getPermissions (gm) {
|
||||
getPermissions (gm: GuildMember): Permissions {
|
||||
if (this.isRoot(gm.id)) {
|
||||
return {
|
||||
isAdmin: true,
|
||||
|
@ -71,18 +113,26 @@ class DiscordService extends Service {
|
|||
}
|
||||
|
||||
return {
|
||||
isAdmin: gm.permissions.hasPermission('ADMINISTRATOR'),
|
||||
canManageRoles: gm.permissions.hasPermission('MANAGE_ROLES', false, true)
|
||||
isAdmin: gm.permissions.has('ADMINISTRATOR'),
|
||||
canManageRoles: gm.permissions.has('MANAGE_ROLES', true)
|
||||
}
|
||||
}
|
||||
|
||||
safeRole (server, role) {
|
||||
const r = this.getRoles(server).get(role)
|
||||
safeRole (server: string, role: string) {
|
||||
const rl = this.getRoles(server)
|
||||
if (rl == null) {
|
||||
throw new Error(`safeRole can't see ${server}`)
|
||||
}
|
||||
const r: ?Role = rl.get(role)
|
||||
if (r == null) {
|
||||
throw new Error(`safeRole can't find ${role} in ${server}`)
|
||||
}
|
||||
|
||||
return r.editable && !r.hasPermission('MANAGE_ROLES', false, true)
|
||||
}
|
||||
|
||||
// oauth step 2 flow, grab the auth token via code
|
||||
async getAuthToken (code) {
|
||||
async getAuthToken (code: string) {
|
||||
const url = 'https://discordapp.com/api/oauth2/token'
|
||||
try {
|
||||
const rsp =
|
||||
|
@ -104,7 +154,7 @@ class DiscordService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
async getUser (authToken) {
|
||||
async getUser (authToken?: string): Promise<UserPartial> {
|
||||
const url = 'https://discordapp.com/api/v6/users/@me'
|
||||
try {
|
||||
if (authToken == null || authToken === '') {
|
||||
|
@ -146,17 +196,17 @@ class DiscordService extends Service {
|
|||
|
||||
// returns oauth authorize url with IDENTIFY permission
|
||||
// we only need IDENTIFY because we only use it for matching IDs from the bot
|
||||
getAuthUrl (state) {
|
||||
getAuthUrl (state: string): string {
|
||||
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&redirect_uri=${this.oauthCallback}&response_type=code&scope=identify&state=${state}`
|
||||
}
|
||||
|
||||
// returns the bot join url with MANAGE_ROLES permission
|
||||
// MANAGE_ROLES is the only permission we really need.
|
||||
getBotJoinUrl () {
|
||||
getBotJoinUrl (): string {
|
||||
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot&permissions=268435456`
|
||||
}
|
||||
|
||||
mentionResponse (message) {
|
||||
mentionResponse (message: Message) {
|
||||
message.channel.send(`🔰 Assign your roles here! <${this.appUrl}/s/${message.guild.id}>`, { disableEveryone: true })
|
||||
}
|
||||
|
||||
|
@ -193,10 +243,11 @@ class DiscordService extends Service {
|
|||
// prefix regex with ^ for ease of code
|
||||
.map(({ regex, ...rest }) => ({ regex: new RegExp(`^${regex.source}`, regex.flags), ...rest }))
|
||||
|
||||
this.cmds = cmds
|
||||
return cmds
|
||||
}
|
||||
|
||||
async handleCommand (message) {
|
||||
async handleCommand (message: Message) {
|
||||
const cmd = message.content.replace(`<@${this.client.user.id}> `, '')
|
||||
this.log.debug(`got command from ${message.author.username}`, cmd)
|
||||
for (let { regex, handler } of this.cmds) {
|
||||
|
@ -218,8 +269,8 @@ class DiscordService extends Service {
|
|||
this.mentionResponse(message)
|
||||
}
|
||||
|
||||
handleMessage (message) {
|
||||
if (message.author.bot && message.channel.type !== 'text') { // drop bot messages and dms
|
||||
handleMessage (message: Message) {
|
||||
if (message.author.bot || message.channel.type !== 'text') { // drop bot messages and dms
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -232,7 +283,7 @@ class DiscordService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
async handleJoin (guild) {
|
||||
async handleJoin (guild: Guild) {
|
||||
await this.ctx.server.ensure(guild)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,49 @@
|
|||
const Service = require('./Service')
|
||||
const LRU = require('lru-cache')
|
||||
// @flow
|
||||
import Service from './Service'
|
||||
import LRU from 'lru-cache'
|
||||
import { type AppContext } from '../Roleypoly'
|
||||
import { type Models } from '../models'
|
||||
import { type ServerModel } from '../models/Server'
|
||||
import type DiscordService, { Permissions } from './discord'
|
||||
import {
|
||||
type Guild,
|
||||
type GuildMember,
|
||||
type Collection
|
||||
} from 'discord.js'
|
||||
import areduce from '../util/areduce'
|
||||
|
||||
export type ServerSlug = {
|
||||
id: string,
|
||||
name: string,
|
||||
ownerID: string,
|
||||
icon: string
|
||||
}
|
||||
|
||||
export type PresentableRole = {
|
||||
id: string,
|
||||
color: number,
|
||||
name: string,
|
||||
position: number,
|
||||
safe: boolean
|
||||
}
|
||||
|
||||
export type PresentableServer = ServerModel & {
|
||||
id: string,
|
||||
gm: {
|
||||
nickname: string,
|
||||
color: string
|
||||
},
|
||||
server: ServerSlug,
|
||||
roles: ?PresentableRole[],
|
||||
perms: Permissions
|
||||
}
|
||||
|
||||
class PresentationService extends Service {
|
||||
constructor (ctx) {
|
||||
cache: LRU
|
||||
M: Models
|
||||
discord: DiscordService
|
||||
|
||||
constructor (ctx: AppContext) {
|
||||
super(ctx)
|
||||
this.M = ctx.M
|
||||
this.discord = ctx.discord
|
||||
|
@ -10,7 +51,7 @@ class PresentationService extends Service {
|
|||
this.cache = new LRU({ max: 500, maxAge: 100 * 60 * 5 })
|
||||
}
|
||||
|
||||
serverSlug (server) {
|
||||
serverSlug (server: Guild): ServerSlug {
|
||||
return {
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
|
@ -19,27 +60,34 @@ class PresentationService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
async oldPresentableServers (collection, userId) {
|
||||
async oldPresentableServers (collection: Collection<string, Guild>, userId: string) {
|
||||
this.log.deprecated('use presentableServers instead of oldPresentableServers')
|
||||
|
||||
let servers = []
|
||||
|
||||
for (let server of collection.array()) {
|
||||
const gm = server.members.get(userId)
|
||||
|
||||
// $FlowFixMe this is deprecated, forget adding more check code.
|
||||
servers.push(await this.presentableServer(server, gm))
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
async presentableServers (collection, userId) {
|
||||
return collection.array().areduce(async (acc, server) => {
|
||||
presentableServers (collection: Collection<string, Guild>, userId: string) {
|
||||
return areduce(collection.array(), async (acc, server) => {
|
||||
const gm = server.members.get(userId)
|
||||
if (gm == null) {
|
||||
throw new Error(`somehow this guildmember ${userId} of ${server.id} didn't exist.`)
|
||||
}
|
||||
|
||||
acc.push(await this.presentableServer(server, gm, { incRoles: false }))
|
||||
return acc
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
async presentableServer (server, gm, { incRoles = true } = {}) {
|
||||
async presentableServer (server: Guild, gm: GuildMember, { incRoles = true }: { incRoles: boolean } = {}): Promise<PresentableServer> {
|
||||
const sd = await this.ctx.server.get(server.id)
|
||||
|
||||
return {
|
||||
|
@ -56,7 +104,7 @@ class PresentationService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
async rolesByServer (server) {
|
||||
async rolesByServer (server: Guild, sd: ServerModel): Promise<PresentableRole[]> {
|
||||
return server.roles
|
||||
.filter(r => r.id !== server.id) // get rid of @everyone
|
||||
.map(r => ({
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
const Service = require('./Service')
|
||||
// @flow
|
||||
import Service from './Service'
|
||||
import { type AppContext } from '../Roleypoly'
|
||||
import type PresentationService from './presentation'
|
||||
import {
|
||||
type Guild
|
||||
} from 'discord.js'
|
||||
import { type ServerModel, type Category } from '../models/Server'
|
||||
|
||||
class ServerService extends Service {
|
||||
constructor (ctx) {
|
||||
export default class ServerService extends Service {
|
||||
Server: any // Model<ServerModel> but flowtype is bugged
|
||||
P: PresentationService
|
||||
constructor (ctx: AppContext) {
|
||||
super(ctx)
|
||||
this.Server = ctx.M.Server
|
||||
this.P = ctx.P
|
||||
}
|
||||
|
||||
async ensure (server) {
|
||||
async ensure (server: Guild) {
|
||||
let srv
|
||||
try {
|
||||
srv = await this.get(server.id)
|
||||
|
@ -24,19 +33,19 @@ class ServerService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
create ({ id, message, categories }) {
|
||||
create ({ id, message, categories }: ServerModel) {
|
||||
const srv = this.Server.build({ id, message, categories })
|
||||
|
||||
return srv.save()
|
||||
}
|
||||
|
||||
async update (id, newData) {
|
||||
async update (id: string, newData: $Shape<ServerModel>) { // eslint-disable-line no-undef
|
||||
const srv = await this.get(id, false)
|
||||
|
||||
return srv.update(newData)
|
||||
}
|
||||
|
||||
async get (id, plain = true) {
|
||||
async get (id: string, plain: boolean = true) {
|
||||
const s = await this.Server.findOne({
|
||||
where: {
|
||||
id
|
||||
|
@ -50,17 +59,17 @@ class ServerService extends Service {
|
|||
return s.get({ plain: true })
|
||||
}
|
||||
|
||||
async getAllowedRoles (id) {
|
||||
const server = await this.get(id)
|
||||
async getAllowedRoles (id: string) {
|
||||
const server: ServerModel = await this.get(id)
|
||||
const categories: Category[] = (Object.values(server.categories): any)
|
||||
const allowed: string[] = []
|
||||
|
||||
return Object.values(server.categories).reduce((acc, c) => {
|
||||
for (const c of categories) {
|
||||
if (c.hidden !== true) {
|
||||
return acc.concat(c.roles)
|
||||
allowed.concat(c.roles)
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return allowed
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServerService
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
const Service = require('./Service')
|
||||
// @flow
|
||||
import Service from './Service'
|
||||
import { type AppContext } from '../Roleypoly'
|
||||
|
||||
class SessionsService extends Service {
|
||||
constructor (ctx) {
|
||||
type SessionData = {
|
||||
rolling: boolean,
|
||||
maxAge: number,
|
||||
changed: boolean
|
||||
}
|
||||
|
||||
export default class SessionsService extends Service {
|
||||
Session: any
|
||||
constructor (ctx: AppContext) {
|
||||
super(ctx)
|
||||
this.Session = ctx.M.Session
|
||||
}
|
||||
|
||||
async get (id, { rolling }) {
|
||||
async get (id: string, { rolling }: SessionData) {
|
||||
const user = await this.Session.findOne({ where: { id } })
|
||||
|
||||
if (user === null) {
|
||||
|
@ -16,7 +25,7 @@ class SessionsService extends Service {
|
|||
return user.data
|
||||
}
|
||||
|
||||
async set (id, data, { maxAge, rolling, changed }) {
|
||||
async set (id: string, data: any, { maxAge, rolling, changed }: SessionData) {
|
||||
let session = await this.Session.findOne({ where: { id } })
|
||||
if (session === null) {
|
||||
session = this.Session.build({ id })
|
||||
|
@ -30,7 +39,7 @@ class SessionsService extends Service {
|
|||
return session.save()
|
||||
}
|
||||
|
||||
async destroy (id) {
|
||||
async destroy (id: string) {
|
||||
const sess = await this.Session.findOne({ where: { id } })
|
||||
|
||||
if (sess != null) {
|
||||
|
@ -38,5 +47,3 @@ class SessionsService extends Service {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SessionsService
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue