basic-implementation #1

Open
florent wants to merge 8 commits from basic-implementation into master
3 changed files with 213 additions and 0 deletions
Showing only changes of commit 866578f422 - Show all commits

45
src/discordParser.ts Normal file
View File

@ -0,0 +1,45 @@
import { Channel, Client } from 'discord.js';
import { isBooleanObject } from 'util/types';
import { ImplementableApi } from './implementableApi';
namespace DiscordParser {
export type Config = ImplementableApi.Config & {
token: string;
};
}
class DiscordParser extends ImplementableApi {
readonly token: string;
readonly channelid: string;
client: Client;
constructor(readonly config: DiscordParser.Config) {
super(config);
this.token = config.token;
// this.settingEvents();
this.client = new Client();
this.instantiateClient();
}
private instantiateClient() {
this.client.login(this.token);
}
public receivedMessage(message: ImplementableApi.Message) {
switch (message.type) {
case 'newEntriesNotify':
default:
break;
}
}

Toujours utile ?

Toujours utile ?
private sendMsg(message: string) {
// this.client.channels.resolveID();

youtubeChannel / peertubeChannel comme nom

`youtubeChannel` / `peertubeChannel` comme nom
}
private settingEvents(): void {
throw 'empty';
type Channels = {
  channelYtb: TextChannel;
  channelPeerTube: TextChannel;
};

static async instanciate(readonly config: DiscordParser.Config) {
  const client = new Client();
  client.login(this.token);
  await eventsOnce(client, 'ready');
  const channels = {
    channelYtb: this.client.channels.resolve("..."),
    channelPeerTube: ...
  };
  return new Discord({
    client,
    channels,
    keyword
  });
```ts type Channels = { channelYtb: TextChannel; channelPeerTube: TextChannel; }; ``` ```ts static async instanciate(readonly config: DiscordParser.Config) { const client = new Client(); client.login(this.token); await eventsOnce(client, 'ready'); const channels = { channelYtb: this.client.channels.resolve("..."), channelPeerTube: ... }; return new Discord({ client, channels, keyword }); ```

I made a thing like this

namespace DiscordParser {
    /*...*/
    export type ConstructorPattern = ImplementableApi.Config & {
        keyWord: string;
        channels: {
            ChannelYtb: TextChannel;
            ChannelPeerTube: TextChannel;
        };
        client: Client;
    };
}
/*...*/
class DiscordParser extends ImplementableApi {
    readonly keyWord: string;
    readonly channels: {
        [key: string]: TextChannel;
        ChannelYtb: TextChannel;
        ChannelPeerTube: TextChannel;
    };
    readonly client: Client;

    constructor(readonly config: DiscordParser.ConstructorPattern) {
        super(config);
        this.keyWord = config.keyWord;
        this.channels = config.channels;
        this.client = config.client;
        this.settingEvents();
    }

    static async instanciate(
            config: DiscordParser.Config
        ): Promise<DiscordParser.ConstructorPattern> {
            const client = new Client();
            client.login(config.token);
            await eventsOnce(client, 'ready');
            const channels: ChannelsType = {
                ChannelPeerTube: this.getTextChannel(
                    client,
                    config.channelsId.idChannelPeerTube
                ),
                ChannelYtb: this.getTextChannel(
                    client,
                    config.channelsId.idChannelYtb
                ),
            };

            return {
                name: config.name,
                channels: channels,
                client: client,
                keyWord: config.keyWord,
            };
        }

        private static getTextChannel(client: Client, id: string): TextChannel {
            const channel = client.channels.resolve(id);
            if (!channel || !(channel instanceof TextChannel))
                throw 'Bad token or bad channel id. Be careful, the channel must be a TextChannel';
            return channel;
        }
    /*...*/
};

The problem with this is for the usage inside the router.
I need to make a thing like this :

class Router {
    api_array: { [key: string]: ImplementableApi } = {};
    readonly routes: Router.Config;

    constructor(readonly config: Router.GlobalConfig) {
        this.routes = config.router;

        this.api_array[config.discord.name] = new DiscordParser(
            await DiscordParser.instanciate(config.discord)
        );
        this.api_array[config.peertubeRequester.name] = new PeerTubeRequester(
            config.peertubeRequester
        );
        this.api_array[config.logWriter.name] = new LogWriter(config.logWriter);
    }
	/*...*/
}

So I have again an async function inside my constructor, and to solved this I need to create the same pattern inside the router class.
And by extension i need to generalize this for my ImplementableAPI class. It's included to force all the users of the ImplementableAPI to make an Dependence Injection Pattern for their API. And it's look a little bit restrictive to me.

We could try maybe an events who's called at the the end of the constructor. (I took this idea from DiscordJS)
Like that we could imagine a thing like this :

class MyClass extends ImplementableAPI {
	constructor(...) {
		super(...);
		const promise = startAnAsynchronousTask(...);
        doSomeSynchronousTask(...);
        promise.then(() = > {
        	this.emit('ready');
        });
        
	}
    
    private startAnAsynchronousTask(...) : Promise<void> {
    	await something(..);
        await somethingElse();
        await events.once(anObject, 'ready');
	}
	/*...*/	
}

With this, the restriction is to call the event 'ready' at the end of the constrctor's tasks. It's looking more friendly than the Dependency Injection for a beginner who would to make his own ImplementableAPI.

I made a thing like this ```ts namespace DiscordParser { /*...*/ export type ConstructorPattern = ImplementableApi.Config & { keyWord: string; channels: { ChannelYtb: TextChannel; ChannelPeerTube: TextChannel; }; client: Client; }; } /*...*/ class DiscordParser extends ImplementableApi { readonly keyWord: string; readonly channels: { [key: string]: TextChannel; ChannelYtb: TextChannel; ChannelPeerTube: TextChannel; }; readonly client: Client; constructor(readonly config: DiscordParser.ConstructorPattern) { super(config); this.keyWord = config.keyWord; this.channels = config.channels; this.client = config.client; this.settingEvents(); } static async instanciate( config: DiscordParser.Config ): Promise<DiscordParser.ConstructorPattern> { const client = new Client(); client.login(config.token); await eventsOnce(client, 'ready'); const channels: ChannelsType = { ChannelPeerTube: this.getTextChannel( client, config.channelsId.idChannelPeerTube ), ChannelYtb: this.getTextChannel( client, config.channelsId.idChannelYtb ), }; return { name: config.name, channels: channels, client: client, keyWord: config.keyWord, }; } private static getTextChannel(client: Client, id: string): TextChannel { const channel = client.channels.resolve(id); if (!channel || !(channel instanceof TextChannel)) throw 'Bad token or bad channel id. Be careful, the channel must be a TextChannel'; return channel; } /*...*/ }; ``` The problem with this is for the usage inside the router. I need to make a thing like this : ```ts class Router { api_array: { [key: string]: ImplementableApi } = {}; readonly routes: Router.Config; constructor(readonly config: Router.GlobalConfig) { this.routes = config.router; this.api_array[config.discord.name] = new DiscordParser( await DiscordParser.instanciate(config.discord) ); this.api_array[config.peertubeRequester.name] = new PeerTubeRequester( config.peertubeRequester ); this.api_array[config.logWriter.name] = new LogWriter(config.logWriter); } /*...*/ } ``` So I have again an async function inside my constructor, and to solved this I need to create the same pattern inside the router class. And by extension i need to generalize this for my ImplementableAPI class. It's included to force all the users of the ImplementableAPI to make an Dependence Injection Pattern for their API. And it's look a little bit restrictive to me. We could try maybe an events who's called at the the end of the constructor. (I took this idea from DiscordJS) Like that we could imagine a thing like this : ```ts class MyClass extends ImplementableAPI { constructor(...) { super(...); const promise = startAnAsynchronousTask(...); doSomeSynchronousTask(...); promise.then(() = > { this.emit('ready'); }); } private startAnAsynchronousTask(...) : Promise<void> { await something(..); await somethingElse(); await events.once(anObject, 'ready'); } /*...*/ } ``` With this, the restriction is to call the event 'ready' at the end of the constrctor's tasks. It's looking more friendly than the Dependency Injection for a beginner who would to make his own ImplementableAPI.

Ok, my bad. I think I missunderstanded the Dependency Injection. Sleep on it.
I'm going to try another thing.
I keep my DiscordParser Class with the instantiate method. And I 'm going to insert the created object inside an method in Router who could be named 'inject' or something in this idea. And who could take an ImplementableAPI instance in params.

Ok, my bad. I think I missunderstanded the Dependency Injection. Sleep on it. I'm going to try another thing. I keep my DiscordParser Class with the instantiate method. And I 'm going to insert the created object inside an method in Router who could be named 'inject' or something in this idea. And who could take an ImplementableAPI instance in params.
}
}
export { DiscordParser };

51
src/logWriter.ts Normal file
View File

@ -0,0 +1,51 @@
import { ImplementableApi } from './implementableApi';
import {
appendFileSync,
closeSync,
openSync,
writeFile,
writeFileSync,
} from 'fs';
namespace LogWriter {
export type Config = ImplementableApi.Config & {
path: string;
};
}
class LogWriter extends ImplementableApi {
readonly path: string;
constructor(readonly config: LogWriter.Config) {
super(config);
this.path = config.path;
this.firstWrite();
}
private firstWrite() {
this.writeMsg('LogWriter is running');
}
private writeMsg(message: string): void;
private writeMsg(message: ImplementableApi.Message): void;
private writeMsg(message: ImplementableApi.Message | string) {
if (typeof message !== 'string')
message = `[${message.type} :: ${new Date().toLocaleString(
'fr-FR'
)}] ${message.rawContent} ${
message.idListener ?? `( listener_id : ${message.idListener} )`
}`;
const fd = openSync(this.path, 'a');
const str = `[${new Date().toLocaleString('fr-FR')}] ${message}`;
appendFileSync(fd, str);
closeSync(fd);
}
public receivedMessage(message: ImplementableApi.Message) {
this.writeMsg(message);
}
}
export { LogWriter };

117
src/peertubeRequester.ts Normal file
View File

@ -0,0 +1,117 @@
import { ImplementableApi } from './implementableApi';
namespace PeerTubeRequester {
export type Config = ImplementableApi.Config & {
domain_name: string;
username: string;
password: string;
};
}
type UploadInstruction = {
[key: string]: string;
channelID: string;
targetUrl: string;
name: string;
description: string;
originallyPublishedAt: string;
thumbnailsfile: string;
};
type ClientToken = {
client_id: string;
client_secret: string;
grant_type: 'password';
response_type: 'code';
username: string;
password: string;
};
type UserToken = {
access_token: string;
token_type: string;
expires_in: string;
refresh_token: string;
};
class PeerTubeRequester extends ImplementableApi {
readonly domain_name: string;
readonly username: string;
readonly password: string;
constructor(readonly config: PeerTubeRequester.Config) {
super(config);
this.domain_name = config.domain_name;
this.username = config.username;
this.password = config.password;
}
public receivedMessage(message: ImplementableApi.Message) {
switch (message.type) {
case 'newEntriesNotify':
this.newEntriesNotify(message);
break;
default:
break;
}
}
private newEntriesNotify(message: ImplementableApi.Message) {
// parse content
const items = message.rawContent['items'];
if (Array.isArray(items))
items.forEach((item) => {
const media_group = item['media:group'];
const args: UploadInstruction = {
channelID: 'undefined', // to do binding avec les idDeChaines Skeptikom
description: media_group['media:description'][0],
name: media_group['media:title'][0],
originallyPublishedAt: item.pubDate,
targetUrl: media_group['media:content'][0]['$']['url'],
thumbnailsfile:
media_group['media:thumbnail'][0]['$']['url'],
};
this.apiRequest(args);
});
}
private async apiRequest(message: UploadInstruction) {
// Auth
const client_info: ClientToken = {
...(await (
await fetch(
`https://${this.domain_name}/api/v1/oauth-clients/local`
)
).json()),
...{
grant_type: 'password',
response_type: 'code',
username: this.username,
password: this.password,
},
};
const myAuthForm = new FormData();
for (const key in client_info) myAuthForm.append(key, message[key]);
const tokens_info: UserToken = await (
await fetch(`https://${this.domain_name}/api/v1/users/token`, {
method: 'get',
body: myAuthForm,
})
).json();
// Upload
const myUploadForm = new FormData();
const myHeader = new Headers();
myHeader.append('Authorization', `Bearer ${tokens_info.access_token}`);
for (const key in message) myUploadForm.append(key, message[key]);
await fetch(`https://${this.domain_name}/api/v1/videos/imports`, {
method: 'post',
headers: myHeader,

Un commentaire ici, c'est compenser à mon sens un problème sémantique dans le code.

J'aurais tendance à faire deux fonctions privées this.authenticate() et this.upload()

Un commentaire ici, c'est compenser à mon sens un problème sémantique dans le code. J'aurais tendance à faire deux fonctions privées `this.authenticate()` et `this.upload()`
body: myUploadForm,
});
}
}
export { PeerTubeRequester };