Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"singleQuote": true,
"printWidth": 120,
"printWidth": 100,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": true,
"semi": true,
"endOfLine" : "auto"
"endOfLine": "auto"
}
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
"@commitlint/config-conventional": "^12.1.4",
"@types/file-type": "^10.9.1",
"@types/jest": "^26.0.23",
"@types/jsdom": "^16.2.11",
"@types/node": "^16.3.1",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.7",
"@types/styled-components": "^5.1.10",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"@types/jsdom": "^16.2.11",
"concurrently": "^6.2.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
Expand All @@ -49,13 +50,14 @@
"typescript": "^4.3.4"
},
"dependencies": {
"dotenv": "^10.0.0",
"file-type": "^16.5.0",
"jsdom": "^16.6.0",
"juice": "^8.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"styled-components": "^5.3.0",
"dotenv": "^10.0.0",
"undici": "^4.0.0",
"jsdom": "^16.6.0"
"telegraf": "^4.3.0",
"undici": "^4.0.0"
}
}
24 changes: 24 additions & 0 deletions src/commands/retrieveMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import airtable from '../../infrastructure/store/airtable';
import dotenv from 'dotenv';
import request from '../infrastructure/network/undiciRequest';
import { Store } from '../infrastructure/store';
import { setup as telegramSetup } from '../infrastructure/telegram/telegram';
dotenv.config();

const AIRTABLE_API_KEY = process.env.AIRTABLE_API_KEY || '';
const AIRTABLE_BASE = process.env.AIRTABLE_BASE || '';
const AIRTABLE_TABLE = process.env.AIRTABLE_TABLE || '';

const store: Store = airtable({
apiKey: AIRTABLE_API_KEY,
app: AIRTABLE_BASE,
table: AIRTABLE_TABLE,
request,
});

const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || '';
const telegramMessageListener = telegramSetup(TELEGRAM_BOT_TOKEN);
telegramMessageListener((message: any) => {
// HERE THE PROCESS TO STORE THE MESSAGES TO AIRTABLE
console.error(message);
});
5 changes: 4 additions & 1 deletion src/core/templating/__test__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import index from '../../../assets/htmlTemplate';
describe('templating', () => {
it('it should render the template', async () => {
const html = renderer(
{ articles: [], episode: { title: 'titolo', description: 'desc', image: 'image url', link: 'link_url' } },
{
articles: [],
episode: { title: 'titolo', description: 'desc', image: 'image url', link: 'link_url' },
},
index as React.FC,
);
expect(html).toMatchSnapshot();
Expand Down
3 changes: 2 additions & 1 deletion src/core/templating/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { NewsletterContent } from '../entities';

type TemplateT = React.FC<{ data: NewsletterContent }>;

const declassify = (html: string): string => html.replace(/( )?class="[a-zA-Z0-9:;.\s()\-,]*"/g, '');
const declassify = (html: string): string =>
html.replace(/( )?class="[a-zA-Z0-9:;.\s()\-,]*"/g, '');

const renderer = (data: NewsletterContent, template: TemplateT): string | undefined => {
const sheet = new ServerStyleSheet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ describe('buildMCMessageFromMail', () => {

test('attachImage must accept only images', () => {
const mail = new Mail();
expect(mail.attachImage(Buffer.from('simple ascii string', 'ascii'), 'myEmbeddedImage')).rejects.toThrowError(
Error,
);
expect(
mail.attachImage(Buffer.from('simple ascii string', 'ascii'), 'myEmbeddedImage'),
).rejects.toThrowError(Error);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ describe('sender flow', () => {
const resourceId = await mail.attachImage(Buffer.from(BASE64_GIF, 'base64'), 'myEmbeddedImage');

mail
.setHTML(`<h1>Hello World</h1><p>This is an image: <img src="cid:${resourceId}" alt="embedded image"></p>`)
.setHTML(
`<h1>Hello World</h1><p>This is an image: <img src="cid:${resourceId}" alt="embedded image"></p>`,
)
.setPlainText('Hello world this is an image: embedded image')
.setFrom({ address: 'test@test.it', name: 'Test' })
.addTo({ address: 'to@test.it', name: 'receiver test' })
Expand Down
11 changes: 7 additions & 4 deletions src/infrastructure/delivery/provider/Mailchimp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,13 @@ export class Sender implements Provider {
} catch (e) {
const fails = mail
.getToRecipient()
.reduce((accumulator: Record<string, string>, current: EmailAddressT): Record<string, string> => {
accumulator[current.address] = 'fails';
return accumulator;
}, {});
.reduce(
(accumulator: Record<string, string>, current: EmailAddressT): Record<string, string> => {
accumulator[current.address] = 'fails';
return accumulator;
},
{},
);
return {
status: 'error',
message: { fails, success: {}, pending: {}, extra: e.toString() },
Expand Down
5 changes: 4 additions & 1 deletion src/infrastructure/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Entry } from '../../core/entities/index';

export interface Store {
save(entries: Entry[]): Promise<boolean>;
create: (entries: Entry[]) => Promise<boolean>;
setSent: (ids: string[]) => Promise<boolean>;
getToSend: () => Promise<Array<Entry>>;
getById: (id: string) => Promise<Entry>;
}
33 changes: 33 additions & 0 deletions src/infrastructure/telegram/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Message represent a message sent to the bot
// it's an abstraction on the full message struct. see https://core.telegram.org/bots/api#messageentity

// This number may have more than 32 significant bits and some programming languages may have
// difficulty/silent defects in interpreting it.
// But it has at most 52 significant bits, so a signed 64-bit integer or double-precision
// float type are safe for storing this identifier
export type TelegramID = number;

export interface User {
id?: TelegramID;
username?: string;
}

export interface Chat {
id: TelegramID;
title?: string;
}

export interface Message {
id: TelegramID;
from: User;
chat: Chat;
date: string; // iso8601
hashtags: string[];
urls: string[];
text: string;
}

export const messageHasHashtags = (message: Message): boolean => message.hashtags.length > 0;
export const messageHasUrls = (message: Message): boolean => message.urls.length > 0;
export const messageIsFromRecognizedAuthor = (message: Message, author: TelegramID): boolean =>
message.from.id === author;
32 changes: 32 additions & 0 deletions src/infrastructure/telegram/normalizeMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Message as apiMessage, MessageEntity } from 'typegram';
import { Message } from './index';

const extractEntitiesFromText =
(text: string) =>
(e: MessageEntity): string =>
text.substring(e.offset, e.offset + e.length);

export const normalizeTelegramMessage = (apiMessage: apiMessage.TextMessage): Message => {
const hashtags = (apiMessage.entities || [])
.filter((e) => e.type === 'hashtag')
.map(extractEntitiesFromText(apiMessage.text));
const urls = (apiMessage.entities || [])
.filter((e) => e.type === 'url')
.map(extractEntitiesFromText(apiMessage.text));

return {
id: apiMessage.message_id,
chat: {
id: apiMessage.chat.id,
title: apiMessage.chat.type === 'group' ? apiMessage.chat.title : undefined,
},
urls: urls,
hashtags: hashtags,
from: {
id: apiMessage.from?.id,
username: apiMessage.from?.username,
},
date: new Date(apiMessage.date).toISOString(),
text: apiMessage.text,
};
};
33 changes: 33 additions & 0 deletions src/infrastructure/telegram/telegram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Telegraf } from 'telegraf';
import { Message, messageHasHashtags, messageHasUrls } from '.';
import { normalizeTelegramMessage } from './normalizeMessages';

interface Process {
(message: Message): void;
}

interface Listener {
(process: Process): void;
}

export const setup = (botToken: string): Listener => {
const bot = new Telegraf(botToken);
return (p: Process): void => {
bot.on('message', (ctx) => {
const message = ctx.update.message;
if ('text' in message) {
const normalizedMessage = normalizeTelegramMessage(message);
if (messageHasHashtags(normalizedMessage) && messageHasUrls(normalizedMessage)) {
p(normalizedMessage);
ctx.reply(
'Attenzione, questo messaggio potrebbe finire tra i link della newsletter, contatta gli admin se non ti interessa che faccia quella fine',
);
}
}
});
bot.launch();
console.log('bot launched');
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));
};
};
3 changes: 2 additions & 1 deletion src/utils/date.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const date2YodaTime = (datetime: Date): string => datetime.toISOString().replace(/T/, ' ').split('.')[0];
export const date2YodaTime = (datetime: Date): string =>
datetime.toISOString().replace(/T/, ' ').split('.')[0];
Loading