Giter VIP home page Giter VIP logo

telegram-bot's Introduction

Hi there 👋 I'm Jey

GitHub stats Top Langs

telegram-bot's People

Contributors

adi-itgg avatar nmago avatar renovate[bot] avatar vendelieu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

telegram-bot's Issues

ForceReply wrong parameter name.

Parameter name must be corrected according to the api.

actually sent message:
"reply_markup":{"input_field_place_holder":
telegram bot api:
"input_field_placeholder":

Processing freezes

Strange behaviour with coroutines on 5.x.x versions due to moving to multiplatfrom.
Need to do more detailed check and find solution.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

github-actions
.github/workflows/ci.build.yml
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3-beta
  • codecov/codecov-action v4
.github/workflows/ci.dev.yml
  • actions/checkout v4
.github/workflows/ci.docs.yml
  • actions/checkout v4
  • actions/setup-java v4
.github/workflows/ci.pre.release.yml
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3-beta
.github/workflows/ci.release.yml
  • actions/checkout v4
  • jacobtomlinson/gha-find-replace v3
  • stefanzweifel/git-auto-commit-action v5
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3-beta
  • actions/checkout v4
  • actions/setup-java v4
  • actions/checkout v4
  • taiki-e/create-gh-release-action v1
gradle
buildSrc/src/main/kotlin/CONST.kt
buildSrc/src/main/kotlin/Kdokker.kt
buildSrc/src/main/kotlin/PublishingExt.kt
buildSrc/src/main/kotlin/ScGenerator.kt
buildSrc/src/main/kotlin/TaskCommons.kt
gradle.properties
settings.gradle.kts
build.gradle.kts
buildSrc/settings.gradle.kts
buildSrc/build.gradle.kts
buildSrc/src/main/kotlin/publish.gradle.kts
gradle/libs.versions.toml
  • io.ktor:ktor-client-core 2.3.9
  • io.ktor:ktor-client-logging 2.3.9
  • io.ktor:ktor-client-java 2.3.9
  • io.ktor:ktor-client-curl 2.3.9
  • io.ktor:ktor-client-winhttp 2.3.9
  • io.ktor:ktor-client-js 2.3.9
  • com.soywiz.korge:korlibs-logger 5.4.0
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.3
  • org.jetbrains.kotlinx:kotlinx-datetime 0.5.0
  • org.jetbrains.kotlin:kotlin-reflect 1.9.23
  • co.touchlab:stately-concurrent-collections 2.0.7
  • com.soywiz.korlibs.krypto:krypto 4.0.10
  • io.kotest:kotest-runner-junit5 5.8.1
  • io.kotest:kotest-assertions-core 5.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.0
  • org.jetbrains.dokka:dokka-base 1.9.20
  • com.google.devtools.ksp:symbol-processing-api 1.9.23-1.0.19
  • com.squareup:kotlinpoet 1.16.0
  • com.squareup:kotlinpoet-ksp 1.16.0
  • io.mockk:mockk 1.13.10
  • org.jetbrains.kotlin.multiplatform 1.9.23
  • org.jetbrains.kotlin.plugin.serialization 1.9.23
  • org.jetbrains.kotlinx.binary-compatibility-validator 0.14.0
  • org.jetbrains.dokka 1.9.20
  • org.jetbrains.kotlinx.kover 0.7.6
  • org.jmailen.kotlinter 4.3.0
  • io.gitlab.arturbosch.detekt 1.23.6
ksp/build.gradle.kts
ktgram-utils/build.gradle.kts
telegram-bot/build.gradle.kts
  • org.jetbrains.kotlinx:kotlinx-coroutines-slf4j 1.8.0
  • ch.qos.logback:logback-classic 1.5.3
webapps/build.gradle.kts
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.7

  • Check this box to trigger a request for Renovate to run again on this repository

Incorrect approach to processing webhooks.

Describe the bug

The proposed webhook handling approach is not suitable because it suggests assigning processing behavior in a controller.
So it would lead to defining the processing behavior every time.

Expected behavior

Reorganize update processing and make it more flexible.

Regex escape sequences lead to incorrect generated code

Describe the bug
If I use \s in command like @RegexCommandHandler("/go\\s") I get a compilation error

Expected behavior
The string remains as is in the generated code and does not lead to a compilation error

I assume that the issue is code is generated somehow manually instead of using specialized libraries like KotlinPoet

Idea for a new way to handle conversations

Is your feature request related to a problem? Please describe.
I'm always frustrated when I have to define conversations using input handlers.
When you are using input handlers, it's pretty hard to write and manage complex conversations.

Describe the solution you'd like
Instead of input handlers, we could use classes for defining conversations. So we are able to write more well-architected code, even when it is complex.

Example:

package com.example.blank.conversation

import eu.vendeli.tgbot.TelegramBot
import eu.vendeli.tgbot.api.message
import eu.vendeli.tgbot.types.User
import eu.vendeli.tgbot.types.internal.ProcessedUpdate

sealed class GreetingsConversation {
    object Start : GreetingsConversation() {
        fun handle(update: ProcessedUpdate, user: User, bot: TelegramBot) {
            message("Hello, let's move to second step!").send(user, bot)
            //set secondStep state
           //maybe something like 
           setData(user, "hello" to update.text)
           setState(user, GreetingsConversation::start)
        }
    }

    object SecondStep : GreetingsConversation() {
        fun handle(update: ProcessedUpdate, user: User, bot: TelegramBot) {
            // retrieve data from start step and proceed
            message("Nice job! You completed this.").send(user, bot)
            deleteState(user, GreetingsConversation)
        }
    }
}

Additional context
Not sure, if we can do this way.
We discussed about it in Telegram group.

Test coverage

Increase test coverage, create a more convenient flow of tests.

  • Cover with tests api methods as much as possible.

Actions are not found in controller if it has public unannotated methods

Describe the bug
I've added a non-private helper method to a controller. I didn't make it private by mistake, but this occasion made TelegramActionsCollector find a total of 2 actions instead of 6 expected. So I commented out this new method and then all actions were found. I didn't find any documentation on that case, so I suppose it isn't intended behavior

To Reproduce

  1. Create controller with an action. Make sure your action is found during start-up process
  2. Add another public method to your controller
  3. Not during start-up, your action won't be found

Expected behavior
I expect that any amount of public/private methods could be added to controller without interfering with work of telegram bot api library

Screenshots

With public helper method, 2 actions in total

image

With private helper method, 6 actions in total
image

Additional context
No stacktrace or error log were produced
P.S. I do enjoy your lib

Hangs-up while running a sample bot on Windows 10 machine

Describe the bug
I created sample application and run it from IntelliJ IDEA. Bot works few seconds and replies on commands. But CPU utilization is very high memory usage is increasing

To Reproduce
Steps to reproduce the behavior:

  1. Try to create sample application
  2. Just wait for 10 seconds after bot starts
  3. Bot becomes non-responsible

Expected behavior
Bot is able to reply on commands unless application is running

Settings
JDK 21
build.gradle.kts (also tried to switch to 1.9.22):

plugins {
    kotlin("jvm") version "1.9.21"
    id("com.google.devtools.ksp") version "1.9.21-1.0.15"
}

dependencies {
    implementation("eu.vendeli:telegram-bot:4.3.0")
    ksp("eu.vendeli:ksp:4.3.0")
}

main.kt (it doesn't matter if any command is defined):

suspend fun main() {
    val bot = TelegramBot(TOKEN) {
        logging {
            botLogLevel = LogLvl.DEBUG
        }
    }

    bot.handleUpdates()
}

@CommandHandler(["/start"])
suspend fun start(user: User, bot: TelegramBot) {
    message { "Hello Human" }.send(user, bot)
}

Additional context
Logs:

18:29:10.517 [main] INFO eu.vendeli.tgbot.core.TgUpdateHandler -- 
CommandHandlers:
(/start, message) - InvocationMeta(qualifier=, function=start, rateLimits=RateLimits(period=0, rate=0))
InputHandlers:
None
RegexCommandHandlers:
None
UpdateTypeHandlers:
None
UnprocessedHandler:
None
18:29:10.522 [main] DEBUG eu.vendeli.tgbot.core.TgUpdateHandler -- The listener is stopped.
18:29:10.523 [main] DEBUG eu.vendeli.tgbot.core.TgUpdateHandler -- The listener is set.
18:29:10.523 [main] DEBUG eu.vendeli.tgbot.core.TgUpdateHandler -- Starting updates collector.
18:29:10.524 [main] INFO eu.vendeli.tgbot.core.TgUpdateHandler -- Starting long-polling listener.
18:29:10.527 [DefaultDispatcher-worker-1] DEBUG eu.vendeli.tgbot.core.TgUpdateHandler -- Running listener with offset - 0
18:29:12.276 [DefaultDispatcher-worker-1] DEBUG eu.vendeli.tgbot.TelegramBot -- RequestBody: {"offset":0,"timeout":20}
18:29:34.885 [DefaultDispatcher-worker-50] DEBUG eu.vendeli.tgbot.core.TgUpdateHandler -- Running listener with offset - 0
18:29:34.885 [DefaultDispatcher-worker-50] DEBUG eu.vendeli.tgbot.TelegramBot -- RequestBody: {"offset":0,"timeout":20}
18:30:00.283 [DefaultDispatcher-worker-29] DEBUG eu.vendeli.tgbot.core.TgUpdateHandler -- Running listener with offset - 0
18:30:00.283 [DefaultDispatcher-worker-29] DEBUG eu.vendeli.tgbot.TelegramBot -- RequestBody: {"offset":0,"timeout":20}

`ClassManagerImpl` class could be open

When I want use library in SpringBoot, to make library's DI works with SpringBoot‘s DI, I have to write:

@Configuration
class BeanCreator(
    @Value("\${telegram.bot.token:}")
    private val botToken: String,
    private val context: ConfigurableApplicationContext
) {
    @Bean
    fun getBot(): TelegramBot {
        val bot = TelegramBot(botToken, "xxx.botController") {
            classManager = object : ClassManager {
                private val instances by lazy { mutableMapOf<String, Any>() }
                override fun getInstance(clazz: Class<*>, vararg initParams: Any?): Any =
                    context.getBean(clazz)?: instances.getOrElse(clazz.name) {
                        if (initParams.isEmpty()) clazz.declaredConstructors.first().newInstance()
                        else {
                            clazz.declaredConstructors.first().newInstance(initParams)
                        }.also { instances[clazz.name] = it }
                    }
            }
        }
        bot.update.setBehaviour {
            handle(it)
        }
        return bot
    }
}

In this case, I want to provide a ClassManager but don't acturally want to implement the ClassManager.
Maybe we should make ClassManagerImpl open so that users can inherit the class to make small modification. In this case, the code could be write as following:

@Configuration
class BeanCreator(
    @Value("\${telegram.bot.token:}")
    private val botToken: String,
    private val context: ConfigurableApplicationContext
) {
    @Bean
    fun getBot(): TelegramBot {
        val bot = TelegramBot(botToken, "xxx.botController") {
            classManager = object : ClassManagerImpl {
                override fun getInstance(clazz: Class<*>, vararg initParams: Any?): Any =
                    context.getBean(clazz)?: super.getInstance(clazz,initParams)
            }
        }
        bot.update.setBehaviour {
            handle(it)
        }
        return bot
    }
}
// I'm not quite familiar with kotlin, so I'm not sure this code could work.

Anyway, maybe this code can add into the SpringBoot demo to show how to work with SpringBoot?

Send File using FileId

Hello,

Firstly, I want to commend you on the excellent work you've done with this project.
Secondly, I have a question: Is it possible to send a video or photo using a fileId instead of uploading it each time? Are there any options available for this?

If the file is already stored somewhere on the Telegram servers, you don't need to reupload it: each file object has a file_id field, simply pass this file_id as a parameter instead of uploading. There are no limits for files sent this way.

Thank you.

Command Tree

Come up with a flexible mechanism for parsing a command through a tree of characters.

`setWebhook` with `certificate` broken

Describe the bug

The following code can't work as expected.

setWebhook("https://site.com/bots").options {
  certificate = ResourceUtils.getFile("classpath:site.com.pem")
}.send(bot)

Telegram need inputfile but we provided the local path of the file.

To Reproduce

setWebhook("https://site.com/bots").options {
  certificate = ResourceUtils.getFile("classpath:site.com.pem")
}.send(bot)

https://api.telegram.org/bot[TOKEN]/getwebhookinfo: "has_custom_certificate":false, is always false.

Help with API - setMyCommands

Hello,
I've already tried .js implementations and nodejs http request to get some positive result when using setMyCommands. The request/response says O.K. but nothing was changed (tried several settings with @telegram‧botFather on my bot, as well).

If telegram-bot can show that setMyCommands works, I would have to switch to Kotlin.

P.S. clever, the interface BotWaitingInput

Library Enhancement

[Affect method] declineChatJoinRequest and approveChatJoinRequest and ?(Maybei more)

[Suggestion] Parameter for User and Chat following convention:

  • type String: username (@ghost);
  • type Long: user ID / chat ID;
  • type User/Chat: the object.
  • We'd better provide 2 method for each API: [type String + type User/Chat] / [type Long + type User/Chat], with type User/Chat, we parse necessary parameter in library.

[Step] Maybe we should slow update this:

  1. Mark the origin API as deprecated, develop new API and suggest them.
  2. Maintain all APIs in some versions.
  3. Delete the deprecated API and release a big version.

How to use custom entities via EntitiesContextBuilder

I am trying to send message in next way:

        val id = "some_id"
        message {
            "Your id:" - code { id }
        }.options {
            parseMode = ParseMode.MarkdownV2
        }.markup {
            inlineKeyboardMarkup {
                "Yes" callback "a"
                "No" callback "b"
            }
        }.send(user, bot)

I want my message look like:

Your id: some_id
[Yes][No]

And I can achieve that by manually wrapping strings in proper symbols, but I want to know how to use EntitiesContextBuilder functions and are they supposed to be used for that purpose?

Thanks in advance :)

Voice deserialization

Issue with Voice type deserialization because of wrong mimeType type.

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `eu.vendeli.tgbot.types.media.Sticker` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('audio/ogg') at [Source: (StringReader); line: 84, column: 333] (through reference chain: eu.vendeli.tgbot.types.internal.Response$Success["result"]->java.util.ArrayList[82]->eu.vendeli.tgbot.types.Update["message"]->eu.vendeli.tgbot.types.Message["voice"]->eu.vendeli.tgbot.types.media.Voice["mime_type"]) at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63) at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1733) at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1358) at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:311) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1500) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:197) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:564) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:564) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:564) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:359) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:564) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:220) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187) at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:144) at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:110) at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263) at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74) at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4730) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3677) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3660) at eu.vendeli.tgbot.TelegramBot.pullUpdates$telegram_bot(TelegramBot.kt:147) at eu.vendeli.tgbot.TelegramBot$pullUpdates$1.invokeSuspend(TelegramBot.kt) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at io.ktor.utils.io.internal.CancellableReusableContinuation.resumeWith(CancellableReusableContinuation.kt:93) at io.ktor.utils.io.ByteBufferChannel.resumeReadOp(ByteBufferChannel.kt:2100) at io.ktor.utils.io.ByteBufferChannel.tryTerminate$ktor_io(ByteBufferChannel.kt:388) at io.ktor.utils.io.ByteBufferChannel.close(ByteBufferChannel.kt:133) at io.ktor.utils.io.CoroutinesKt$launchChannel$1.invoke(Coroutines.kt:145) at io.ktor.utils.io.CoroutinesKt$launchChannel$1.invoke(Coroutines.kt:144) at kotlinx.coroutines.InvokeOnCompletion.invoke(JobSupport.kt:1392) at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1520) at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:323) at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:240) at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:906) at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:863) at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:828) at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:100) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42) at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664) Stacktrace:

Improve Kdoc of api methods.

Add mirror documentation for telegram api methods to make method hints more indicative.

All telegram api methods can be found in api directory.

Also, when copying documentation from telegram api, do not forget to change the format of api parameters from snake_case to camelCase.

ClassManagerImpl throw IllegalArgumentException wrong number of arguments.

The basic ClassicManagerImpl in some environments takes an empty list as an argument and gives IllegalArgumentException wrong number of arguments.

To Reproduce
Steps to reproduce the behavior:
I used to get this behavior when trying to deplocate a bot on a heroku.

Expected behavior
The same work of designers in all environments

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.