mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-10 03:32:52 +00:00
feat: email client prototype
This commit is contained in:
2
backend/mail-plugin/app.js
Normal file
2
backend/mail-plugin/app.js
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
import './lib/cli.js';
|
||||
68
backend/mail-plugin/package.json
Normal file
68
backend/mail-plugin/package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@openstapps/mail-plugin",
|
||||
"description": "Mail Plugin",
|
||||
"version": "3.2.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "GPL-3.0-only",
|
||||
"author": "Thea Schöbl",
|
||||
"bin": "app.js",
|
||||
"files": [
|
||||
"app.js",
|
||||
"lib",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"Dockerfile"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup-node --dts",
|
||||
"deploy": "pnpm --prod --filter=@openstapps/minimal-plugin deploy ../../.deploy/minimal-plugin",
|
||||
"dev": "tsup-node --watch --onSuccess \"pnpm run start\"",
|
||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint --ext .ts src/",
|
||||
"lint:fix": "eslint --fix --ext .ts src/",
|
||||
"start": "node app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openstapps/core": "workspace:*",
|
||||
"@openstapps/core-tools": "workspace:*",
|
||||
"@openstapps/logger": "workspace:*",
|
||||
"commander": "10.0.0",
|
||||
"dotenv": "16.4.5",
|
||||
"express": "4.18.2",
|
||||
"imapflow": "1.0.162",
|
||||
"mailparser": "3.7.1",
|
||||
"node-forge": "1.3.1",
|
||||
"nodemailer": "6.9.14",
|
||||
"ts-node": "10.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openstapps/eslint-config": "workspace:*",
|
||||
"@openstapps/prettier-config": "workspace:*",
|
||||
"@openstapps/tsconfig": "workspace:*",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/imapflow": "1.0.18",
|
||||
"@types/mailparser": "3.4.4",
|
||||
"@types/node": "18.15.3",
|
||||
"@types/node-forge": "1.3.11",
|
||||
"@types/nodemailer": "6.4.15",
|
||||
"tsup": "6.7.0",
|
||||
"typescript": "5.4.2"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/cli.ts"
|
||||
],
|
||||
"sourcemap": true,
|
||||
"clean": true,
|
||||
"format": "esm",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"prettier": "@openstapps/prettier-config",
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@openstapps"
|
||||
]
|
||||
}
|
||||
}
|
||||
123
backend/mail-plugin/src/cli.ts
Normal file
123
backend/mail-plugin/src/cli.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import {config} from 'dotenv';
|
||||
import {ImapFlow} from 'imapflow';
|
||||
import express from 'express';
|
||||
|
||||
config({path: '.env.local'});
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 4000;
|
||||
|
||||
app.use(async (request, response, next) => {
|
||||
try {
|
||||
const [user, pass] = Buffer.from(request.headers['authorization']!.replace(/^Basic /, ''), 'base64')
|
||||
.toString('utf8')
|
||||
.split(':');
|
||||
|
||||
const client = new ImapFlow({
|
||||
host: 'imap.server.uni-frankfurt.de',
|
||||
port: 993,
|
||||
secure: true,
|
||||
emitLogs: false,
|
||||
auth: {user, pass},
|
||||
});
|
||||
response.locals.client = client;
|
||||
|
||||
await client.connect();
|
||||
response.on('finish', async () => {
|
||||
await client.logout();
|
||||
client.close();
|
||||
});
|
||||
|
||||
next();
|
||||
} catch {
|
||||
response.status(401).send();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', async (_request, response) => {
|
||||
const result = await response.locals.client.listTree();
|
||||
response.json(result);
|
||||
});
|
||||
|
||||
app.get('/:mailbox', async (request, response) => {
|
||||
try {
|
||||
await response.locals.client.mailboxOpen(request.params.mailbox);
|
||||
const since = Number(request.query.since) || undefined;
|
||||
const preData = await response.locals.client.status(request.params.mailbox, {messages: true});
|
||||
if (preData.messages === 0) {
|
||||
response.json([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = response.locals.client.fetch(
|
||||
'1:*',
|
||||
{},
|
||||
{
|
||||
// caution, BigInt can throw
|
||||
changedSince: typeof since === 'string' ? BigInt(since) : undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const messages = [];
|
||||
for await (const message of data) {
|
||||
messages.push(message.seq.toString());
|
||||
}
|
||||
response.json(messages);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
response.status(404).send();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/:mailbox/:id', async (request, response) => {
|
||||
try {
|
||||
await response.locals.client.mailboxOpen(request.params.mailbox);
|
||||
const message = await response.locals.client.fetchOne(request.params.id, {
|
||||
envelope: true,
|
||||
labels: true,
|
||||
flags: true,
|
||||
bodyStructure: true,
|
||||
});
|
||||
response.json({
|
||||
bodyStructure: message.bodyStructure,
|
||||
labels: [...(message.labels ?? [])],
|
||||
flags: [...(message.flags ?? [])],
|
||||
envelope: message.envelope,
|
||||
seq: message.seq,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
response.status(404).send();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/:mailbox/:id/:part', async (request, response) => {
|
||||
try {
|
||||
await response.locals.client.mailboxOpen(request.params.mailbox, {readOnly: true});
|
||||
if (request.query.raw) {
|
||||
const message = await response.locals.client.fetchOne(request.params.id, {
|
||||
bodyParts: [`${request.params.part}.mime`, request.params.part],
|
||||
});
|
||||
|
||||
response.write(message.bodyParts.get(`${request.params.part}.mime`));
|
||||
response.write(message.bodyParts.get(request.params.part));
|
||||
|
||||
response.end();
|
||||
} else {
|
||||
const message = await response.locals.client.download(request.params.id, request.params.part);
|
||||
message.content.on('data', chunk => {
|
||||
response.write(chunk);
|
||||
});
|
||||
message.content.on('end', () => {
|
||||
response.end();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
response.status(404).send();
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening on port ${port}`);
|
||||
});
|
||||
9
backend/mail-plugin/src/types.d.ts
vendored
Normal file
9
backend/mail-plugin/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import {ImapFlow} from 'imapflow';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Locals {
|
||||
client: ImapFlow;
|
||||
}
|
||||
}
|
||||
}
|
||||
4
backend/mail-plugin/tsconfig.json
Normal file
4
backend/mail-plugin/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@openstapps/tsconfig",
|
||||
"exclude": ["lib", "app.js"]
|
||||
}
|
||||
Reference in New Issue
Block a user