Skip to content

Commit

Permalink
Aggressively align everything with linting
Browse files Browse the repository at this point in the history
Files were rearranged, JS was moved to TS, etc. Massive changes, but
hopefully not to any functionality.
  • Loading branch information
Shadowfiend committed Oct 21, 2022
1 parent 7a32d32 commit d2af0a9
Show file tree
Hide file tree
Showing 34 changed files with 1,458 additions and 1,191 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
root: true,
env: { mocha: true },
extends: ["@thesis-co"],
}
1 change: 1 addition & 0 deletions .tsconfig-eslint.json
198 changes: 198 additions & 0 deletions lib/Job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import * as scheduler from "node-schedule"
import * as hubot from "hubot"
import { Matrix, MatrixMessage } from "hubot-matrix"
import cronParser from "cron-parser"
import util from "util"
// eslint-disable-next-line import/no-cycle
import processTemplateString from "./template-strings"
import CONFIG from "./schedule-config"

export function isCronPattern(pattern: string | Date) {
if (pattern instanceof Date) {
return false
}
const { errors } = cronParser.parseString(pattern)
return !Object.keys(errors).length
}

export type JobUser = Pick<hubot.User, "id" | "name"> & { room: string }

export type MessageMetadata = {
threadId?: string
messageId: string
lastUrl?: string
}

export function urlFor(
roomId: string,
serverName: string,
eventId: string,
): string {
return `https://matrix.to/#/${roomId}/${eventId}?via=${serverName}`
}

export function updateJobInBrain(
robotBrain: hubot.Brain<hubot.Adapter>,
storageKey: string,
job: Job,
): ReturnType<Job["serialize"]> {
const serializedJob = job.serialize()
// eslint-disable-next-line no-return-assign, no-param-reassign
return (robotBrain.get(storageKey)[job.id] = serializedJob)
}

export async function postMessageAndSaveThreadId(
robot: hubot.Robot<Matrix>,
envelope: Omit<hubot.Envelope, "message">,
job: Job,
messageText: string,
) {
if (robot.adapter.constructor !== Matrix) {
return
}

const postedEvent = await robot.adapter.sendThreaded(
{ ...envelope, message: new hubot.TextMessage(envelope.user, "", "") },
// Though the job may have an associated thread id, reminders spawn their
// own threads.
undefined,
messageText,
)

if (postedEvent === undefined) {
throw new Error("Unexpected undefined Matrix client")
}

const threadId = postedEvent.event_id
const eventId = postedEvent.event_id
const url = urlFor(envelope.room, "thesis.co", eventId)

if (CONFIG.dontReceive !== "1") {
// Send message to the adapter, to allow hubot to process the message.
const messageObj = new MatrixMessage(envelope.user, messageText, "", {
threadId,
})
robot.adapter.receive(messageObj)
}

// Update the job in memory, and ensure metadata exists
// eslint-disable-next-line no-param-reassign
job.metadata.lastUrl = url

// Update the job in brain and log the update.
const serializedJob = updateJobInBrain(
robot.brain,
RECURRING_JOB_STORAGE_KEY,
job,
)
logSerializedJobDetails(
robot.logger,
serializedJob,
"Updated job's last url after posting latest occurrence",
job.id,
)
}

export default class Job {
public user: JobUser

public job: any

constructor(
public id: string,
public pattern: string,
user: JobUser,
public room: string,
public message: string,
private cb: (() => void) | undefined,
public metadata: MessageMetadata,
public remindInThread = false,
) {
this.user = {
room: room || user.room,
name: user.name,
id: user.id,
}
}

isCron(): boolean {
return isCronPattern(this.pattern)
}

start(robot: hubot.Robot<hubot.Adapter>) {
// eslint-disable-next-line no-return-assign
return (this.job = scheduler.scheduleJob(this.pattern, () => {
const { lastUrl: _, threadId, ...threadlessMetadata } = this.metadata
const envelope = {
user: new hubot.User(this.user.id, this.user),
room: this.user.room,
message: new hubot.Message(new hubot.User(this.user.id, this.user)),
metadata: this.remindInThread
? { ...threadlessMetadata, threadId }
: (threadlessMetadata as MessageMetadata),
}

let processedMessage = ""
try {
processedMessage = processTemplateString(
this.message,
robot.brain,
this,
)
} catch (error) {
robot.logger.error(
`Problem processing message at job start: ${util.inspect(error)}`,
)
// Do not throw error since this will fail invisibly to the user. Return unprocessed message.
// However, we should not hit this case since job creation will fail with an error.
processedMessage = this.message
}

if (!isCronPattern(this.pattern)) {
// Send via adapter if the job is a DateTime, not recurring job (these
// get deleted after sending, so there is no way to look up their
// lastUrl).
robot.adapter.send(envelope, processedMessage)

if (CONFIG.dontReceive !== "1") {
// Send message to the adapter, to allow hubot to process the message.
// We handle this case in the postMessageCallback for all API-posted jobs.
const messageObj = new MatrixMessage(
new hubot.User(this.user.id, this.user),
processedMessage,
"",
// Datetime jobs created via `remind` retain thread_id in metadata.
envelope.metadata,
)
robot.adapter.receive(messageObj)
}
} else {
try {
// Recurring jobs should post via API instead, so we can save thread id.
postMessageAndSaveThreadId(robot, envelope, this, processedMessage)
} catch (err) {
robot.logger.error("Error posting scheduled message", err)
}
}

return typeof this.cb === "function" ? this.cb() : undefined
}))
}

cancel() {
if (this.job) {
scheduler.cancelJob(this.job)
}
return typeof this.cb === "function" ? this.cb() : undefined
}

serialize(): readonly [string, JobUser, string, MessageMetadata, boolean] {
return [
this.pattern,
this.user,
this.message,
this.metadata,
this.remindInThread,
] as const
}
}
13 changes: 8 additions & 5 deletions lib/adapter-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ function getRoomIdFromName(
const rooms = robotAdapter.client?.getRooms()
roomIDByLowercaseName =
rooms?.reduce(
(roomIDByLowercaseName, room) => (
(roomIDByLowercaseName[room.normalizedName.toLowerCase()] =
room.roomId),
roomIDByLowercaseName
// eslint-disable-next-line no-return-assign
(acc, room) => (
// eslint-disable-next-line no-sequences
(acc[room.normalizedName.toLowerCase()] = room.roomId), acc
),
{} as typeof roomIDByLowercaseName,
) ?? roomIDByLowercaseName

return roomIDByLowercaseName[lowercaseRoomName]
}
return undefined
}

/**
Expand All @@ -64,6 +65,7 @@ function getRoomNameFromId(
if (isMatrixAdapter(robotAdapter)) {
return robotAdapter.client?.getRoom(roomId)?.name
}
return undefined
}

export type RoomInfo = {
Expand Down Expand Up @@ -99,14 +101,15 @@ function getRoomInfoFromIdOrName(

if (matchingRoom) {
return {
roomId: matchingRoom.id,
roomId: matchingRoom.roomId,
roomName: matchingRoom.name,
accessType:
matchingRoom.getJoinRule() === JoinRule.Public
? "public"
: "non-public",
}
}
return undefined
}

/**
Expand Down
71 changes: 38 additions & 33 deletions lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
// Provides a collection of configuration validation helpers

import { Adapter } from "hubot"
import { getRoomInfoFromIdOrName, isRoomNameValid } from "./adapter-util"

/**
* Given a config key and an issueReporter:
*
* checks whether the config value is set, and returns it if so.
*
* If the config value is not set:
* - if the robot is using the shell adapter, logs and returns an empty string.
* - if the robot is using any other adapter, throws an error.
*/
export function fetchConfigOrReportIssue(
configKey: string,
issueReporter: (errorMessage: string) => void,
): string {
if (!process.env[configKey]) {
issueReporter(`Could not get necessary value for configKey: ${configKey}.`)
}

return process.env[configKey] || ""
}

/**
* Given a robot and a set of config keys, reads all of the config values as
* with [fetchConfigOrReportIssue]. If `fetchConfigOrReportIssue` completes
Expand Down Expand Up @@ -34,7 +55,7 @@ export function withConfigOrReportIssues(
.filter((_) => _.length > 0)

return (valueHandler: (...configValues: string[]) => void) => {
if (values.length == keys.length) {
if (values.length === keys.length) {
valueHandler(...values)
}
}
Expand All @@ -50,45 +71,21 @@ export function withConfigOrReportIssues(
* - if the robot is using any other adapter, throws an error.
*/
export function fetchRoomInfoOrReportIssue(
robot: Hubot.Robot<any>,
robot: Hubot.Robot<Adapter>,
roomName: string,
): string {
if (!isRoomNameValid(robot.adapter, roomName)) {
robot.logger.warning(
`Could not get flow object for: ${roomName}. This will break the build when connected to flowdock.`,
)
}
return getRoomInfoFromIdOrName(robot.adapter, roomName)?.id
}

/**
* Given a config key and an issueReporter:
*
* checks whether the config value is set, and returns it if so.
*
* If the config value is not set:
* - if the robot is using the shell adapter, logs and returns an empty string.
* - if the robot is using any other adapter, throws an error.
*/
export function fetchConfigOrReportIssue(
configKey: string,
issueReporter: (errorMessage: string) => void,
): string {
if (!process.env[configKey]) {
issueReporter(`Could not get necessary value for configKey: ${configKey}.`)
}

return process.env[configKey] || ""
}

export function issueReporterForRobot(
robot: Hubot.Robot<any>,
): (errorMessage: string) => void {
console.log("Building an issue reporter!")
return (errorMessage: string) => {
console.log("Inside an issue reporter!")
logOrThrow(robot, errorMessage)
const roomId = getRoomInfoFromIdOrName(robot.adapter, roomName)?.roomId
if (roomId === undefined) {
robot.logger.warning(
`Could not get flow object for: ${roomName}. This will break the build when connected to flowdock.`,
)
}
return roomId ?? ""
}

/**
Expand All @@ -97,7 +94,7 @@ export function issueReporterForRobot(
* - if the robot is using the shell adapter, logs and returns an empty string.
* - if the robot is using any other adapter, throws an error.
*/
function logOrThrow(robot: Hubot.Robot<any>, errorMessage: string) {
function logOrThrow(robot: Hubot.Robot<Adapter>, errorMessage: string) {
if (robot.adapterName.toLowerCase() !== "flowdock") {
// this is local dev, just log it
robot.logger.warning(
Expand All @@ -109,6 +106,14 @@ function logOrThrow(robot: Hubot.Robot<any>, errorMessage: string) {
throw new Error(errorMessage)
}

export function issueReporterForRobot(
robot: Hubot.Robot<Adapter>,
): (errorMessage: string) => void {
return (errorMessage: string) => {
logOrThrow(robot, errorMessage)
}
}

module.exports = {
withConfigOrReportIssues,
fetchRoomInfoOrReportIssue,
Expand Down
2 changes: 2 additions & 0 deletions lib/flowdock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// eslint-disable-next-line max-classes-per-file, import/no-named-default
import { default as axios, AxiosResponse, AxiosRequestConfig } from "axios"

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types available.
import { Base64 } from "js-base64"

Expand Down
Loading

0 comments on commit d2af0a9

Please sign in to comment.