evilsprut / nestjs-telegraf Goto Github PK
View Code? Open in Web Editor NEW๐ค Powerful Nest module for easy and fast creation Telegram bots
Home Page: https://nestjs-telegraf.vercel.app/
License: MIT License
๐ค Powerful Nest module for easy and fast creation Telegram bots
Home Page: https://nestjs-telegraf.vercel.app/
License: MIT License
Hi,
first thing, thanks a lot for this library.
I'm trying to add a middleware to the bot, but I see it is not accessible outside the TelegrafService.
Wouldn't it make sense to have public readonly bot
at this line instead of having it private?
Then it would be possible to call bot.use
as showed here
I can make a PR if you wish
I have 2 bots which are initialized in app.module.ts
:
TelegrafModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
botName: 'admin',
useFactory: async (config: ConfigService) => {
return {
middlewares: [adminSessions.middleware()],
token: config.get<string>('BOT_ADMIN_TOKEN'),
include: [BotModule]
}
}
}),
TelegrafModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
botName: 'client',
useFactory: async (config: ConfigService) => {
return {
middlewares: [clientSessions.middleware()],
token: config.get<string>('BOT_CLIENT_TOKEN'),
include: [BotModule]
}
}
}),
And I have bot.module.ts
which is
@Module({
providers: [
{
provide: AdminBotUpdate,
useFactory: (bot: Telegraf<Context>) => {
return new AdminBotUpdate(bot);
},
inject: [getBotToken('admin')]
}, {
provide: ClientBotUpdate,
useFactory: (bot: Telegraf<Context>) => {
return new ClientBotUpdate(bot);
},
inject: [getBotToken('client')]
}
]
})
export class BotModule {}
and it should work with @Update() class
with admin.update.ts
and client.update.ts
:
@Update()
@Injectable()
export class AdminBotUpdate {
constructor(
@InjectBot('admin') private adminBot: Telegraf<Context>
) {}
@Hears('hi')
async hears(@Ctx() ctx: Context) {
await ctx.reply('Hey from admin');
}
}
@Update()
@Injectable()
export class ClientBotUpdate {
constructor(
@InjectBot('client') private clientBot: Telegraf<Context>
) {}
@Hears('hi')
async hears(@Ctx() ctx: Context) {
await ctx.reply('Hey from client');
}
}
but there is no answer in bots. If I change BotModule
to
@Module({
providers: [
AdminBotUpdate,
ClientBotUpdate
]
})
export class BotModule {}
is working just with admin.update.ts
If you register several handlers (not strictly the same) only the first defined is being called:
app.update.ts:
import { On, Start, Update } from 'nestjs-telegraf';
import { Context } from 'telegraf';
@Update()
export class MessageHandler {
@Start()
async start(ctx: Context) {
await ctx.reply('start');
}
@On('message')
async handler1(ctx: Context) {
await ctx.reply('handler 1');
}
@On('message')
async handler2(ctx: Context) {
await ctx.reply('handler 2');
}
}
I would expect that all these matching handlers will be called. Is it designed to behave like this? E.g.:
usr> /start
bot> start
bot> handler 1
bot> handler 2
8.15.0
v16.17.1
"nestjs-telegraf": "^2.4.0",
"telegraf": "^4.8.2"
From node:16-alpine
Code
import { InjectBot } from "nestjs-telegraf";
import { Telegraf, Scenes, Context } from "telegraf";
import { Logger, Injectable, Inject } from "@nestjs/common";
import { Update, Ctx, Start, Help, On, Hears } from "nestjs-telegraf";
@Update()
@Injectable()
export class TgService {
constructor(
@InjectBot() private bot: Telegraf<Scenes.SceneContext>,
) {
process.once("SIGINT", () => bot.stop("SIGINT"));
process.once("SIGTERM", () => bot.stop("SIGTERM"));
}
@Help()
async help(@Ctx() ctx: Context) {
await ctx.reply('Send me a sticker');
}
}
After sending the command(/help
) i get an error: node[268]: ../src/tcp_wrap.cc:149:static void node::TCPWrap::New(const v8::FunctionCallbackInfo<v8::Value>&): Assertion
args[0]->IsInt32()' failed.`
this.bot.command('start', (ctx) => ctx.reply('Hello'));
- if you write like this, everything works fine.
@Update()
- it breaks the code
I am setting multiple instances dynamically and want to inject all inctances by Record<BotNameString, BotInstance>
. Can you implement that or share any ideas how can I make by myself with a PR?
Is there any way to debug if the BOT is receiving updates by webhook URL?
I am using a FREE INSTANCE on Heroku. I would my application wake up on receives a Telegram BOT Update, and unfortunately, this does not happen.
I am following the official documentation about it:
https://nestjs-telegraf.vercel.app/getting-updates
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: isProduction
? ['error', 'log', 'warn']
: ['debug', 'error', 'log', 'verbose', 'warn'],
});
const configService = app.get(ConfigService);
app.useGlobalPipes(new ValidationPipe());
const bot = app.get(getBotToken());
app.use(
bot.webhookCallback(`/${configService.get<string>('TELEGRAM_BOT_TOKEN')}`),
);
await app.listen(process.env.PORT || 3000);
}
bootstrap();
telegram.module.ts
@Module({
imports: [
TelegrafModule.forRootAsync({
useFactory: async (configService: ConfigService) => {
const launchOptions = {
token: configService.get('TELEGRAM_BOT_TOKEN'),
middlewares: [
session(),
telegrafThrottler(),
botEffects(),
new I18n({
defaultLanguageOnMissing: true,
directory: 'locales',
useSession: true,
defaultLanguage: 'pt_br',
}).middleware(),
],
};
if (isProduction) {
return {
...launchOptions,
domain: configService.get<string>('APP_URL'),
hookPath: `/${configService.get<string>('TELEGRAM_BOT_TOKEN')}`,
};
}
return launchOptions;
},
inject: [ConfigService],
}),
UsersModule,
TelegramMenuModule,
],
On Heroku process.env.NODE_ENV
is equal "production".
export const CtxUserName = createParamDecorator((ctx: Context) => {
const username = ctx.message?.from?.username as string
|| ctx.callbackQuery?.from?.username as string
return username
})
Hi.
Unfortunately, I cannot install your library having nestjs version 8.0.
Here is my package.json:
{
"name": "telegram-bot",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "8.0.6",
"@nestjs/core": "8.0.6",
"@nestjs/platform-express": "8.0.6",
"reflect-metadata": "0.1.13",
"rimraf": "3.0.2",
"rxjs": "7.3.0"
},
"devDependencies": {
"@nestjs/cli": "8.1.1",
"@nestjs/schematics": "8.0.3",
"@nestjs/testing": "8.0.6",
"@types/express": "4.17.13",
"@types/jest": "27.0.1",
"@types/node": "16.9.2",
"@types/supertest": "2.0.11",
"@typescript-eslint/eslint-plugin": "4.31.1",
"@typescript-eslint/parser": "4.31.1",
"eslint": "7.32.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-prettier": "3.4.1",
"jest": "27.2.0",
"prettier": "2.4.1",
"supertest": "6.1.6",
"ts-jest": "27.0.5",
"ts-loader": "9.2.5",
"ts-node": "10.2.1",
"tsconfig-paths": "3.11.0",
"typescript": "4.4.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
How to reproduce:
npm i nestjs-telegraf telegraf
Hi, Do you have any examples of webhook usage?
I got telegram messages to my endpoint, but Update and other directives are not working.
Hello!
I would see an example about how to use webhooks ๐
This is what I tried:
app.module.ts
TelegrafModule.forRootAsync({
useFactory: async (configService: ConfigService) => {
const telegramOptions: TelegrafModuleOptions = {
token: configService.get('nest.TELEGRAM_BOT'),
middlewares: [
telegrafThrottler(),
sendLogToChannel(
configService.get('nest.LOG_TELEGRAM_BOT_CHANNEL'),
),
],
};
if (isDevelopment) {
return {
...telegramOptions,
launchOptions: {
webhook: {
domain: '11111111111.ngrok.io',
port: 8080,
},
},
};
}
return telegramOptions;
},
inject: [ConfigService],
}),
My nestjs application is listening on port 3000 and I am using NGROK to get an HTTPS address on port 8080.
When I configure it this way, no error is displayed on the screen, but the bot commands just don't work. It doesn't work only means that I don't receive anything back.
I have no idea how to use wizard on my scene.
Please add more examples.
For using test server you can
modify the token string like this
1111111111:AAAAAAAAAAAAAAAAAAAAAAAAAA/test
I am using telegraf-test library for e2e testing. It is my first experience, and I dont know which libraries may be better for that.
After including webhook to the server via constructor, my testing script successful sent signals to them and application ran methods with sending telegram messages to me. But in this case axios request (from telegraf-test) respond with empty data.
Hello, I'm trying the small piece of code on the README, but events are never fired. I'm decorating the class with @update(), the method for the /start command with @start() and so on for commands with @command(). The constructor of that class is correctly fired and I see no errors. Any idea?
import { Update, Start, Help, Command, Ctx, Hears } from 'nestjs-telegraf';
import { Context } from 'telegraf';
import { AppService } from './app.service';
@Update()
export class AppUpdate {
constructor(private readonly appService: AppService) {
console.log('constructor fired');
}
@Start()
onStart(): string {
console.log('onStart() fired');
return "Welcome!";
}
}
Hey!
https://docs.nestjs.com/fundamentals/injection-scopes#request-provider
I am developing a plugin for nestjs-telegraf
(still in alpha)
https://github.com/felinto-dev/tg-menu
And I have an advanced use-case. I need to inject the Telegraf request in a class like is possible in HTTP. Check the link above.
How I could do that using nestjs-telegraf?
Example
Expected solution:
import { Injectable, Scope, Inject } from '@nestjs/common';
import { CONTEXT } from '@nestjs/graphql';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(CONTEXT) private context) {}
}
"@nestjs/core": "8.4.1",
"nestjs-telegraf": "^2.6.1",
"telegraf": "^4.9.1",
"typescript": "^4.8.2"
`node_modules/nestjs-telegraf/dist/types/index.d.ts:6:158 - error TS2344: Type 'T[U]' does not satisfy the constraint '(...args: any) => any'.
Type 'T[OnlyFunctionPropertyNames]' is not assignable to type '(...args: any) => any'.
Type 'T[T[keyof T] extends (...args: any) => any ? keyof T : never]' is not assignable to type '(...args: any) => any'.
Type 'T[keyof T]' is not assignable to type '(...args: any) => any'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type '(...args: any) => any'.
Type 'T[string]' is not assignable to type '(...args: any) => any'.
6 export declare type ComposerMethodArgs<T extends Composer, U extends OnlyFunctionPropertyNames = OnlyFunctionPropertyNames> = Filter<Parameters<T[U]>, Middleware>;`
Hello!
I am using Telegraf 4.3.0 and nestjs-telegraf 2.2.2.
I am creating a bot which uses a custom context, and for doing so I extended Context.
To test if everything works I created a sample @ Start command handler as follows:
When sending /start to the bot I can see the answer correctly displayed but in the log of the server I receive the following message:
context type: telegraf not supported
Am I missing anything in the configuration?
Hey
Tried to use scenes, but got some errors.
Scene init:
@TelegrafUse()
useScenes(ctx, next) {
// Greeter scene
const greeter = new Scene('greeter');
greeter.enter(ctx => ctx.reply('Hi'));
greeter.leave(ctx => ctx.reply('Bye'));
greeter.hears(/hi/gi, leave());
greeter.on('message', ctx => ctx.reply('Send `hi`'));
// Create scene manager
const stage = new Stage();
stage.command('cancel', leave());
// Scene registration
stage.register(greeter);
stage.middleware(ctx);
next();
}
But, when i tried to enter in:
@TelegrafStart()
async startHandler(ctx) {
ctx.scene.enter('greeter');
}
Got this one: Cannot read property 'enter' of undefined.
Hello
I found, that we have only one way of using middlewares
stage.middleware() // register stage
registerUpdates() // register updates (after stage)
But, there is no way to setup @use, which will call before the stage. Or @StageUse - to call use on stage
.
I can create a PR, but i need to understand, which names of decorators to use?
#289 i have an issues with this update
i can't use providers and nested updates.
If i have @Update
in the providers - nested @Update
will not work.
But if i delete providers - everything works fine.
Add support for nestjs v9. It seems no breaking changes are introduced for this lib.
Hi, I want to use some command and action handlers as global so that they work regardless of the user's current scene.
In my telegraph bot I implemented it like this.
export const globalCommands = new Composer<AppContext>();
globalCommands.command('start', startHandler);
globalCommands.command('exit', exitHandler);
bot.use((ctx, next) => {
const scenes: Map<string, BaseScene<AppContext>> = stages.scenes;
const scene = new SceneContextScene<AppContext, AppWizardSession>(
ctx,
scenes,
stages.options,
);
ctx.scene = scene;
return next();
});
bot.use(globalCommands);
bot.use(globalHandlers);
bot.use(stages.middleware());
How do I do this with nestjs-telegraf?
Hello, my issue is more about the question of how to work with scenes?
This part is really not described well in docs.
I was trying to create scene like this
@Update()
@Injectable()
@Scene('mainScene')
export class TbotService {
constructor(
@InjectBot() private bot: Telegraf<any>,
) {
}
@Start()
async startCommand(ctx: SceneContext) {
console.log(this.bot)
await ctx.reply('Welcome ss');
}
}
// other scene
@Update()
@Injectable()
@Scene('authScene')
export class TbotService {
constructor(
@InjectBot() private bot: Telegraf<any>,
) {
}
@SceneEnter()
async sayHi(ctx: SceneContext) {
await ctx.reply('Welcome ss');
}
}
But after I add SceneEnter
decorator, I see an error.
/Users/backend/node_modules/nestjs-telegraf/dist/services/listeners-explorer.service.js:133
composer[method](...args, async (ctx, next) => {
^
TypeError: Function.prototype.apply was called on undefined, which is a undefined and not a function
at ListenersExplorerService.registerIfListener (/Users/backend/node_modules/nestjs-telegraf/dist/services/listeners-explorer.service.js:133:29)
at MapIterator.iteratee (/Users/backend/node_modules/nestjs-telegraf/dist/services/listeners-explorer.service.js:92:82)
at MapIterator.next (/Users/backend/node_modules/iterare/src/map.ts:9:39)
at FilterIterator.next (/Users/backend/node_modules/iterare/src/filter.ts:11:34)
at IteratorWithOperators.next (/Users/backend/node_modules/iterare/src/iterate.ts:19:28)
at Function.from (<anonymous>)
at IteratorWithOperators.toArray (/Users/backend/node_modules/iterare/src/iterate.ts:227:22)
at MetadataScanner.scanFromPrototype (/Users/backend/node_modules/@nestjs/core/metadata-scanner.js:12:14)
at ListenersExplorerService.registerListeners (/Users/backend/node_modules/nestjs-telegraf/dist/services/listeners-explorer.service.js:92:30)
at /Users/backend/node_modules/nestjs-telegraf/dist/services/listeners-explorer.service.js:53:41
Any Ideas, advices ?
After npm install --production
package is broken, because of unresolved lodash.
There are two options for resolving:
@bukhalo, what do you prefer? Do you need help with this issue?
is there an example with callback_data in a button. im not able to get any data from the callback buttons when i move between steps using the wizard.
I used the example from #350 but I cant seem to get the call back data to pass through between steps. any help would be appreciated!
Somewhere in blablabla.update.ts
telegram bot controller:
constructor(
@InjectBot() private bot: TelegrafProvider,
) {}
Error:
[Nest] 87272 - 09/08/2020, 10:22:19 PM [ExceptionHandler] Nest can't resolve dependencies of the BlablablaUpdate (?). Please make sure that the argument TelegrafProvider at index [0] is available in the BlablablaModule context.
Potential solutions:
- If TelegrafProvider is a provider, is it part of the current BlablablaModule?
- If TelegrafProvider is exported from a separate @Module, is that module imported within BlablablaModule?
@Module({
imports: [ /* the Module containing TelegrafProvider */ ]
})
Hello!
I'm currently using nestjs-telegraf though long-polling..
I've implemented using decorators, for example:
@Start()
async startCommand(ctx) {
await this.cache.del(ctx.chat.id.toString());
await ctx.telegram.sendMessage(
ctx.chat.id,
'Favor, informar seu CPF para iniciar o atendimento',
MessageUtils.get(MessageCodeEnum.MENU_HIDE),
);
}
It work just fine.
Now, I'm trying to use webhook. Using webhook I've to use @Ctx()
and type it as Context :
@Post()
public teste(@Ctx() ctx: Context) {
console.log(ctx);
}
The problem is: the ctx object (Context) that I receive by parameter isn't the same that I receive using long-polling.
The method ctx.telegram.sendMessage for example is expecting ExtraReplyMessage
as third parameter, not a string. It's just one example, there're many others changes over ctx object when i use webook endpoint.
I'm using that ctx method over all my code i cant just change to another object class. Please I don't know what to do anymore. I'm searching about it for 2 weeks.
From the github:
import {
Update,
Start,
Help,
On,
Hears,
Context,
} from 'nestjs-telegraf';
import { AppService } from './app.service';
import { Context } from './context.interface';
Shouldn't be the second Context e.g. ContextType
?
Hi,
I have following setup:
AppModule:
TelegrafModule.forRootAsync({
useFactory: (config: ConfigService) => ({
token: config.get<string>('TELEGRAM_TOKEN'),
middlewares: [session()],
}),
inject: [ConfigService],
}),
AppUpdate:
@Update()
export class AppUpdate {
....
@On('message')
async onMessage(ctx: Context<Update>) {
...
}
}
then I have another module where I have another Update class
@Update()
export class OtherUpdate {
....
@Command('command')
async onCommand(ctx: Context<Update>) {
...
}
}
the issues I'm facing is that whenever I type /command in bot chat, it's caught by @on('message) handler in AppUpdate. When I comment out 'message' handler in AppUpdate, everything is working as expected and 'command' is handled properly. Do you have any suggestion how to avoid this?
When I try to use cron from @nestjs/schedule - bot didn't work.
Below you can see my code:
import { Cron } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm';
import { On, Update } from 'nestjs-telegraf';
import { Context } from 'telegraf';
import { Repository } from 'typeorm';
import { TelegramBot } from './entities/bot.entity';
@update()
export class BotService {
constructor(
@InjectRepository(TelegramBot)
private telegramRepo: Repository
) {}
@on('message')
@Cron('*/30 * * * * *')
async message(ctx: Context): Promise {
await ctx.reply('Hello there');
}
}
In terminal I receive:
[Scheduler] TypeError: Cannot read property 'reply' of undefined;
How can I use cron correctly with bot logic (I want receive every 30 seconds message from bot "Hello there" in chat )?
@bukhalo
Hello!
https://nestjs-telegraf.vercel.app/ doesnt work.
this.inlineKeyboard(ctx, {
title: 'message',
buttons: [
[{
text: 'test',
callback_data: 'test_callback'
}
],
]
})
private static async inlineKeyboard(ctx: Context, msg: any): Promise < void > {
await ctx.replyWithMarkdown(msg.title, {
reply_markup: {
inline_keyboard: msg.buttons,
},
parse_mode: 'Markdown'
});
}
@TelegramActionHandler({
message: 'test_callback'
})
protected async debugLogs(ctx: Context) {
console.log('1');
}
I tried to use it like that and nothing happened((
Hello,
Thank you for that amazing project. It is exactly what I was looking for. But, it looks weird that the plugin launches bot in any case.
For example, I'm running my bot on the Firebase and I don't need to launch the bot itself since all requests are handled by the Firebase function and from my end, only bot.handleUpdate()
will be used.
It would be really handy if I could disable launching anyhow. I think the options object is an appropriate place for it.
Thank you
I receive the error UnhandledPromiseRejectionWarning: TypeError: Telegraf: "reply" isn't available for "inline_query"
when I try to use inline queries.
I looked at the project's examples folder and didn't find any examples of how to use inline query.
I believe that this package is returning ctx.reply by default, which causes the error.
This is the code that I use in my application.
@InlineQuery(/s (.*)/)
async processInlineQuery(ctx: Context) {
const inlineQuery = ctx.match[1];
let topics: ForumTopic[];
try {
topics = await this.forumService.findBySearchQuery(inlineQuery);
} catch {
topics = [];
}
await ctx.answerInlineQuery(
topics.map((topic) => ({
type: 'article',
id: String(topic.id),
title: topic.title,
description: `Postado em ${topic.category}`,
input_message_content: {
message_text: `๐ <a href="${topic.url}">${topic.title} [LINK]</a>\n\n๐ Postado em <a href="${topic.categoryUrl}">${topic.category}</a>`,
parse_mode: 'HTML',
},
url: topic.url,
})),
);
}
Is it possible to override catch function for whole bot, not in every place I need it through injecting and redefining .catch()?
This issue provides visibility into Renovate updates and their statuses. Learn more
These updates are currently rate limited. Click on a checkbox below to force their creation now.
@commitlint/cli
, @commitlint/config-angular
)@typescript-eslint/eslint-plugin
, @typescript-eslint/parser
)These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
@nestjs/cli
, @nestjs/common
, @nestjs/core
, @nestjs/platform-express
, @nestjs/testing
)Hello! I wanna use ctx.scene.enter("auth");
, but scene is undefined.
auth.scene.ts
import { Scene, SceneEnter, Command } from 'nestjs-telegraf';
import { Context } from '../../../interfaces/context.interface';
@Scene('auth')
export class AuthScene {
@SceneEnter()
onSceneEnter(): string {
return 'Enter your username and password!';
}
@Command('leave')
async onLeaveCommand(ctx: Context): Promise<void> {
await ctx.scene.leave();
}
}
start.command.ts
import { Update, Start, Ctx } from 'nestjs-telegraf';
import { Context } from '../../../interfaces/context.interface';
@Update()
export class StartCommand {
@Start()
async start(@Ctx() ctx: Context) {
return await ctx.scene.enter('auth');
}
}
context.interface
import { Scenes } from 'telegraf';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Context extends Scenes.SceneContext {}
Thanks.
Screenshot: http://joxi.ru/Dr8ORkjcMgJdzA
hi
i need to override error handling in create-bot-factory.util.ts , for example:
LINE 8: bot.catch(options.handleError)
This code doesn't work:
@TelegramActionHandler({ message: '' })
Hello
Scenes functionality works really good.
But, there is an issue: i can't get ctx.scene inside my middleware before next()
I think, that issue in time, when Scene.Stage of @scene() is called
This example will cause an error if the reply
function is called without async/await:
@TelegramActionHandler({ message: ''})
reply(ctx: ContextMessageUpdate) {
ctx.reply(`You say ${ctx.message.text}`)
}
Package can't be updated automatically #64, need fixes.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.