Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
a3caa2d
converted import syntax to ES modules
Feb 16, 2026
5a39610
got test sort of working (jest set up is not crashing but also not mo…
Feb 16, 2026
5e28cdf
adjusted beforeeach/beforeall so more pass
Feb 17, 2026
876f948
more correct test setup
Feb 17, 2026
5ab0d62
converted import syntax to commonJS
Feb 16, 2026
a44e274
got test sort of working (jest set up is not crashing but also not mo…
Feb 16, 2026
79d98c3
adjusted beforeeach/beforeall so more pass
Feb 17, 2026
0a4bfa5
more correct test setup
Feb 17, 2026
321fe7f
more correct dockerfile and compose.yaml
Feb 17, 2026
35b454b
Revert "converted import syntax to commonJS"
Feb 17, 2026
ba9552b
updated jest to sort of work with es6
Feb 17, 2026
4fcb534
separating out enum return from method return
Feb 18, 2026
5e3b3f3
mostly working except for the weirdest error
Feb 18, 2026
164ff7d
nevermind it wasn't actually working, gonna move on for now
Feb 18, 2026
a4804c2
added babel to convert es modules to cjs
Feb 18, 2026
01e620a
finally figured out issue with tests (referencing the method directly…
Feb 18, 2026
bfc633a
setup fixed more
Feb 18, 2026
0b7f549
added error handling parseMemberCommand test
Feb 18, 2026
c645bb0
renamed db to database
Feb 18, 2026
31eb426
upgraded fluxer.js
Feb 18, 2026
3dbbe7d
moved import to helpers folder
Feb 18, 2026
15703c2
moved import to helpers folder
Feb 18, 2026
fe00f66
more tests for member helper
Feb 18, 2026
1bf6c8c
think i fixed weird error with webhook sending error when a user has …
Feb 18, 2026
23a57b3
simplified sendMessageAsAttachment
Feb 18, 2026
da5a250
added return to addFullMember so that addNewMember can reference it p…
Feb 18, 2026
5c01f2e
test setup for messagehelper and webhookhelper
Feb 18, 2026
f0ac02e
readded line i shouldn't have removed in sendMessageAsMember
Feb 18, 2026
e16694a
fixed test and logic
Feb 18, 2026
152bc88
added test for memberHelper
Feb 18, 2026
400e40a
updated sendMessageAsAttachment to returnBufferFromText and updated c…
Feb 18, 2026
223292c
added tests for parseProxyTags and updated logic
Feb 18, 2026
274f1ea
added "return" so tests dont terminate on failure and deleted env.jest
Feb 18, 2026
da9a3d2
finished tests for messageHelper!
Feb 18, 2026
acd9ce7
more cases for messageHelper just in case
Feb 18, 2026
1bba809
updating docstring for messageHelper parseProxyTags
Feb 18, 2026
fc1c463
more tests for webhookhelper
Feb 18, 2026
9d5493e
Merge remote-tracking branch 'origin/add-tests' into add-tests
Feb 19, 2026
75c4c54
deleted extra file added during merge
Feb 19, 2026
d33c321
removed confusing brackets from enum docs
Feb 19, 2026
873959a
finally mocking correctly
Feb 19, 2026
21efbcc
adding more cases to messageHelper tests
Feb 19, 2026
a7cd4e9
updating enums
Feb 19, 2026
f9199f8
removed error response when proxy is sent without content
Feb 19, 2026
9dab429
, updated tests for webhookHelper and removed error response when pro…
Feb 19, 2026
6eb9fef
added debounce to count guilds properly
Feb 19, 2026
7aeae18
added todo note
Feb 19, 2026
2e0a8ad
added tests for updateDisplayName
Feb 19, 2026
7a3b8c1
edited help message trigger for updatePropic
Feb 19, 2026
849acf7
update message helper test to include space case
Feb 19, 2026
1e2724b
update bot to suppress errors from API
Feb 20, 2026
c4c6ad0
fixed bug for import not sending help text, added help text if you ty…
Feb 20, 2026
fad6d42
updated to be enum
Feb 20, 2026
e719823
updated member helper and tests
Feb 20, 2026
b1a29fd
edit enums, tweak import content command
Feb 20, 2026
d640322
removed unnecessary await and console.log
Feb 20, 2026
b64ba6e
made it easier to make a member
Feb 20, 2026
eb6c606
added nicer error listing to importHelper
Feb 20, 2026
4d3db8c
updated documentation
Feb 20, 2026
e905262
Merge branch 'main' of https://github.com/pieartsy/PluralFlux into ad…
Feb 20, 2026
a30d408
Merge branch 'main' of https://github.com/pieartsy/PluralFlux into ad…
Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ package-lock.json
config.json
coverage
log.txt
.env
.env
oya.png
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["node", "src/bot.js"]
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@ All commands are prefixed by `pf;`. Currently only a few are implemented.

- `pf;help` - Sends the current list of commands.

- `pf;import` - Imports from PluralKit using the JSON file provided by their export command. Importing from other proxy bots is *TBD*. `pf;import` and attach your JSON file to the message. This will only save the fields that are present in the bot currently (the stuff above), not anything else like birthdays or system handles (yet?).
- `pf;import` - Imports from PluralKit using the JSON file provided by their export command. Importing from other proxy bots is *TBD*. `pf;import` and attach your JSON file to the message. This will only save the fields that are present in the bot currently, not anything else like birthdays or system handles (yet?). **Only one proxy can be set per member currently.**"

- `pf;member` - Accesses the sub-commands related to editing proxy members. The available subcommands are:
- `new` - Creates a new member to proxy with, for example: `pf;member new jane`. The member name should ideally be short so you can write other commands with it easily.
You can optionally add a display name after the member name, for example: `pf;member new jane "Jane Doe | ze/hir"`. If it has spaces, put it in __double quotes__. The length limit is 32 characters.
- `new` - Creates a new member to proxy with, for example: `pf;member new jane`. The member name should ideally be short so you can write other commands with it easily. The order of values is `pf;member new [name] [displayname] [proxy] [propic]`, _without brackets_. The name is **required**, but the rest are optional.
Usage notes:
- If anything has spaces, put it in quotes: `"Jane Doe"`
- If anything is unset, and you want to set something after it (for ex: you haven't set a display name, but you want to add a proxy), put the unset value in empty quotes in the same position: "" If you leave it out, the bot will set things wrong.
- The maximum length of a display name is 32 characters.
- You can't use the same proxy for two different members.
- You can also upload an image directly instead of using a url.
Examples:
- Full example: `pf;member new jane "Jane Doe" J:text https://cdn.pixabay.com/photo/2023/10/20/19/07/aster-8330078_1280.jpg`
- Example with gaps: `pf;member new bob "Bob he/him" "" https://cdn.pixabay.com/photo/2016/05/09/11/09/tennis-1381230_1280.jpg

- `remove` - Removes a member based on their name, for example: `pf;member remove jane`.
- `name` - Updates the name for a specific member based on their current name, for ex: `pf;member john name jane`. The member name should ideally be short so you can write other commands with it easily.
- `list` - Lists all members in the system.
Expand All @@ -27,7 +36,7 @@ You can optionally add a display name after the member name, for example: `pf;me
1. Pass in a direct remote image URL, for example: `pf;member jane propic <https://cdn.pixabay.com/photo/2020/05/02/02/54/animal-5119676_1280.jpg>`. You can upload images on sites like <https://imgbb.com/>.
2. Upload an attachment directly.
**NOTE:** Fluxer does not save your attachments forever, so option #1 is recommended.
- `proxy` Updates the proxy tag for a specific member based on their name. The proxy must be formatted with the tags surrounding the word 'text', for example: `pf;member jane proxy Jane:text` or `pf;member amal proxy [text]` This is so the bot can detect what the proxy tags are. Only one proxy can be set per member currently.
- `proxy` Updates the proxy tag for a specific member based on their name. The proxy must be formatted with the tags surrounding the word 'text', for example: `pf;member jane proxy Jane:text` or `pf;member amal proxy [text]` This is so the bot can detect what the proxy tags are. **Only one proxy can be set per member currently.**

## Notes
- Attaching files to messages with the proxy does not work, due to either the limitations of Fluxer itself :(
Expand Down
8 changes: 8 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// babel.config.js
module.exports = {
env: {
test: {
plugins: ["@babel/plugin-transform-modules-commonjs"]
}
}
};
38 changes: 24 additions & 14 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ services:
main:
build: .
container_name: pluralflux
restart: unless-stopped
networks:
- pluralflux-net
postgres:
image: postgres:latest
container_name: pluralflux-postgres
Expand All @@ -13,20 +16,27 @@ services:
- pgdata:/var/lib/postgresql
ports:
- "5432:5432"
# pgadmin:
# image: dpage/pgadmin4:latest
# ports:
# - 5050:80
# environment:
# # Required by pgAdmin
# PGADMIN_DEFAULT_EMAIL: pieartsy@pm.me
# PGADMIN_DEFAULT_PASSWORD_FILE: /run/secrets/postgres_pwd
# # Don't require the user to login
# PGADMIN_CONFIG_SERVER_MODE: 'False'
# # Don't require a "master" password after logging in
# PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
# secrets:
# - postgres_pwd
networks:
- pluralflux-net
pgadmin:
image: dpage/pgadmin4:latest
container_name: pluralflux-pgadmin
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: code@asterfialla.com
PGADMIN_DEFAULT_PASSWORD_FILE: /run/secrets/postgres_pwd
PGADMIN_CONFIG_SERVER_MODE: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
secrets:
- postgres_pwd
depends_on:
- postgres
networks:
- pluralflux-net

networks:
pluralflux-net:

volumes:
pgdata:
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// jest.config.js
module.exports = {
clearMocks: true,
collectCoverage: true,
coverageDirectory: "coverage",
verbose: true,
transform: {
"^.+\\.[t|j]sx?$": require.resolve('babel-jest')
},
};
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
"version": "1.0.0",
"description": "",
"main": "src/bot.js",
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/pieartsy/PluralFlux.git"
},
"private": true,
"dependencies": {
"@fluxerjs/core": "^1.0.9",
"@fluxerjs/core": "^1.1.5",
"dotenv": "^17.3.1",
"pg": "^8.18.0",
"pg-hstore": "^2.3.4",
Expand All @@ -19,6 +18,10 @@
"tmp": "^0.2.5"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
"@babel/preset-env": "^7.29.0",
"babel-jest": "^30.2.0",
"jest": "^30.2.0"
},
"scripts": {
Expand Down
30 changes: 26 additions & 4 deletions src/bot.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, Events, GatewayOpcodes } from '@fluxerjs/core';
import { Client, Events } from '@fluxerjs/core';
import { messageHelper } from "./helpers/messageHelper.js";
import {enums} from "./enums.js";
import {commands} from "./commands.js";
Expand Down Expand Up @@ -34,7 +34,7 @@ client.on(Events.MessageCreate, async (message) => {

const commandName = content.slice(messageHelper.prefix.length).split(" ")[0];
// If there's no command name (ie just the prefix)
if (!commandName) await message.reply(enums.help.SHORT_DESC_PLURALFLUX);
if (!commandName) return await message.reply(enums.help.SHORT_DESC_PLURALFLUX);

const args = messageHelper.parseCommandArgs(content, commandName);

Expand All @@ -44,18 +44,40 @@ client.on(Events.MessageCreate, async (message) => {
throw e
});
}
else {
await message.reply(enums.err.COMMAND_NOT_RECOGNIZED);
}
}
catch(error) {
console.error(error);
return await message.reply(error.message);
// return await message.reply(error.message);
}
});

client.on(Events.Ready, () => {
console.log(`Logged in as ${client.user?.username}`);
console.log(`Serving ${client.guilds.size} guild(s)`);
});

let guildCount = 0;
client.on(Events.GuildCreate, () => {
guildCount++;
callback();
});

function printGuilds() {
console.log(`Serving ${client.guilds.size} guild(s)`);
}

const callback = Debounce(printGuilds, 2000);

function Debounce(func, delay) {
let timeout = null;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), delay);
};
}

try {
await client.login(token);
// await db.check_connection();
Expand Down
19 changes: 12 additions & 7 deletions src/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {messageHelper} from "./helpers/messageHelper.js";
import {enums} from "./enums.js";
import {memberHelper} from "./helpers/memberHelper.js";
import {EmbedBuilder} from "@fluxerjs/core";
import {importHelper} from "./import.js";
import {importHelper} from "./helpers/importHelper.js";

const cmds = new Map();

Expand All @@ -12,13 +12,17 @@ cmds.set('member', {
const authorFull = `${message.author.username}#${message.author.discriminator}`
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
const attachmentExpires = message.attachments.size > 0 ? message.attachments.first().expires_at : null;
const reply = await memberHelper.parseMemberCommand(message.author.id, authorFull, args, attachmentUrl, attachmentExpires).catch(e =>{throw e});
const reply = await memberHelper.parseMemberCommand(message.author.id, authorFull, args, attachmentUrl, attachmentExpires).catch(async (e) =>{await message.reply(e.message);});
if (typeof reply === 'string') {
return await message.reply(reply);
}
else if (reply instanceof EmbedBuilder) {
await message.reply({embeds: [reply.toJSON()]})
}
else if (typeof reply === 'object') {
const errorsText = reply.errors.length > 0 ? reply.errors.join('\n- ') : null;
return await message.reply({content: `${reply.success} ${errorsText ? "\nThese errors occurred:\n" + errorsText : ""}`, embeds: [reply.embed.toJSON()]})
}

}
})
Expand All @@ -45,12 +49,11 @@ cmds.set('help', {

cmds.set('import', {
description: enums.help.SHORT_DESC_IMPORT,
async execute(message) {
if (message.content.includes('--help')) {
async execute(message, client, args) {
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
if ((message.content.includes('--help') || (args[0] === '' && args.length === 1)) && !attachmentUrl ) {
return await message.reply(enums.help.IMPORT);
}
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;

return await importHelper.pluralKitImport(message.author.id, attachmentUrl).then(async (successfullyAdded) => {
await message.reply(successfullyAdded);
}).catch(async (error) => {
Expand All @@ -59,7 +62,9 @@ cmds.set('import', {
let errorsText = `${error.message}.\nThese errors occurred:\n${error.errors.join('\n')}`;

await message.reply(errorsText).catch(async () => {
await messageHelper.sendMessageAsAttachment(errorsText, message);
const returnedBuffer = messageHelper.returnBufferFromText(errorsText);
await message.reply({content: returnedBuffer.text, files: [{ name: 'text.pdf', data: returnedBuffer.file }]
})
});
}
// If just one error was returned.
Expand Down
16 changes: 8 additions & 8 deletions src/db.js → src/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ if (!password) {
process.exit(1);
}

const database = {};
const db = {};

const sequelize = new Sequelize('postgres', 'postgres', password, {
host: 'localhost',
logging: false,
dialect: 'postgres'
});

database.sequelize = sequelize;
database.Sequelize = Sequelize;
db.sequelize = sequelize;
db.Sequelize = Sequelize;

database.members = sequelize.define('Member', {
db.members = sequelize.define('Member', {
userid: {
type: DataTypes.STRING,
allowNull: false,
Expand All @@ -41,7 +41,7 @@ database.members = sequelize.define('Member', {
}
});

database.systems = sequelize.define('System', {
db.systems = sequelize.define('System', {
userid: {
type: DataTypes.STRING,
},
Expand All @@ -59,8 +59,8 @@ database.systems = sequelize.define('System', {
/**
* Checks Sequelize database connection.
*/
database.check_connection = async function() {
await sequelize.authenticate().then(async (result) => {
db.check_connection = async function() {
await sequelize.authenticate().then(async () => {
console.log('Connection has been established successfully.');
await syncModels();
}).catch(err => {
Expand All @@ -81,4 +81,4 @@ async function syncModels() {
});
}

export const db = database;
export const database = db;
12 changes: 7 additions & 5 deletions src/enums.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading