Git Product home page Git Product logo

hedwig's People

Contributors

ayrat555 avatar derhackler avatar docteurklein avatar enilsen16 avatar joshuawscott avatar jwarlander avatar knewter avatar lucperkins avatar naturkultur1 avatar schlenks avatar scrogson avatar supernullset avatar wende avatar wojtekmach 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hedwig's Issues

about max #connections

hi! before all, thanks for your work... i have an eye on this project since almost its first day, very well written, and having a xmpp client somehow proxied server-side is very good/clever idea. congrats for that!

i'm not a sysadmin guy and don't have networking knowledge enough to find an answer by myself, so i'd like to ask you something that maybe is pretty basic. AFAIK there's a max number of (TCP) connections per system, i'd say 64K per client per server port or something like that. so, in the following scenario:

  • mobile/web clients are connecting to a cowboy/phoenix server through websocket connection
  • once the connection is stablished, w/ hedwig we start an xmpp client to
  • (e.g.) an ejabberd server running in the same physical server
    i understand a TCP connection is opened client<>wsServer, and another wsServer(process)<>ejabberd. do you know if those local/internal connections between hedwig and ejabberd are somehow affecting our max number of open connection? in other words, per user we would have 2 connections, then max number of possible user connections: 32K.

also, we know that BEAM also has a max of processes, and having at least 2 (erlang) processes per user is a fact, but this is more tweak-able :)

How to actively send messages?

Hi,

I'm currently trying to build an agent, that is supposed to actively send messages to a HipChat room when certain EC2 instances are running that shouldn't run.

Replying works fine (in the responders), but how can I send a message via the agent actively? I tried:

Hedwig.Robot.send(pid, %{text: "hello world"})

This crashes my agent with a match error.

Best,
Benedikt

how to test responders?

While most of the responder code I'm writing is super simple, I'd still like to be able to add some doctest-style tests. Is there any way to do this?

Making a tutorial / howto regarding adapters

As asked on Twitter, I'm opening this issue. This will also help me clarify what I mean by "a tutorial".

Although the API is quite small, perhaps the authors of the official adapters could write a document about the preferred architecture of an adapter and the best practices (OTP applications, helpful 3rd-party libraries, etc).
ping @scrogson :)

Ability to listen but not respond

I would like to create an IRC logger I would like to do something like this

  hear ~r/.*/i, msg do
    save_to_db(msg)
  end

But if I do this write now each message causes the gen server to die because the hear macro does not end with an emote, reply and send

Process registration: `handle_connect` not being called?

I've been following the README down through the Registering your robot process section in order to register a robot's process by name, but handle_connect doesn't appear to be called and the registration never happens.

This is with the latest master, on Elixir 1.3.4 and Erlang 19. A demo repo is here: https://github.com/stevegrossi/alfred. There's nothing special: to reproduce the issue I hewed as close to the README as possible, using mix hedwig.gen.robot with the name "alfred" and the Console adapter.

For simplicity, I'm attempting to start the robot manually, rather than through a supervision tree. Again, copy-and-pasting from the README's Finding Your Robot section:

screen shot 2016-11-17 at 5 46 53 am

The generated process registration via :global in handle_connect does not appear to take place. And indeed, if I replace the auto-generated handle_connect with something that should obviously fail:

defmodule Alfred.Robot do
  use Hedwig.Robot, otp_app: :alfred

  def handle_connect(state) do
    raise "handle_connect called"
  end

The robot starts just fine, which leads me to believe handle_connect isn't being called at all. Is there possibly an issue here?

Domain issue in connecting

I've got a pretty strange problem.
The JID of my account is [email protected], and the server is foo2.bar.nl. I had this all stored in the config map.
However, the server would always reject the connection after the auth stanza.
I finally found out that it was because in the first stream:stream stanza it should say bar.nl instead of foo2.bar.nl in the to attribute.
Currently I've got it hardcoded in the library, but I thought it would be nicer if this could be implemented in some way.

So this is more a proposal; would you be interested in a PR if I fixed this in one of the following ways:

  • Add a boolean to the config map, determining if the server property of the config map or the domain part of the JID should be used in the to attribute of the stream:stream stanza.
  • Parse the server variable in Hedwig.Stanza.start_stream/2 to only use the bar.nl part when the server variable is foo.bar.nl.

I hope this was clear and look forward to a reply.

Crash when trying to create a secure connection

So my SMTP server is listening on localhost and I chose .plain for the secure option.
Nonetheless, Hedwig tries to establish a TLS secured connection when the server supports it.
And that's where my crash occurs. One of the parameters you pass to TLS.Socketin your starttls method is nil and gets force unwrapped later. I hope that helps fixing the issue, but you should also consider changing the approach here. Why do you want to force a TLS connection just because the server supports it? Some people just want to connect to an SMTP server that is listening on the loopback adapter and I don't get the point of creating an encrypted connection to localhost.

:id required for handlers with one_for_one supervisor strategy

The example configuration in the README errors out when handlers are specified:

** (Mix) Could not start application hedwig: exited in: Hedwig.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (ArgumentError) duplicated id Hedwig.Client found in the supervisor specification, please explicitly pass the :id option when defining this worker/supervisor

Works if any unique id is passed in:

diff --git a/lib/hedwig.ex b/lib/hedwig.ex
index 7c285ce..7663c3b 100644
--- a/lib/hedwig.ex
+++ b/lib/hedwig.ex
@@ -6,7 +6,7 @@ defmodule Hedwig do

     clients = Application.get_env(:hedwig, :clients, [])
     children = for client <- clients, into: [] do
-      worker(Hedwig.Client, [client])
+      worker(Hedwig.Client, [client], [id: make_ref])
     end

     opts = [strategy: :one_for_one, name: Hedwig.Supervisor]

how to deploy to heroku?

I'm blindly trying to have a working Procfile.
However the "web" process type does not seem to work.
I tried:

bot: mix run --no-halt

and

web: mix run --no-halt

same effect. no logs, nothing.

Do you have any experience regarding heroku deployment?
PS: i'm using:

 % heroku config
=== edgar-hedwig Config Vars
BUILDPACK_URL: https://github.com/HashNuke/heroku-buildpack-elixir.git
MIX_ENV:       prod

When trying to include v0.1.0 in my project, dependency resolution for exml fails.

When trying to start a link via a new client like so:

defmodule Thermubots.Device.Messenger do
  use GenServer

  alias Hedwig.Client

  def start_link(settings) do
    config = %{jid: settings.username,
               password: settings.password,
               nickname: settings.id,
               resource: settings.resource}

    GenServer.start_link(__MODULE__, config)
  end

  def init(config) do
    {:ok, client} = Client.start_link(config)
    {:ok, client: client}
  end
end

I get the following:

** (UndefinedFunctionError) undefined function: :exml_stream.new_parser/0 (module :exml_stream is not available)
    :exml_stream.new_parser()
    lib/hedwig/transports/tcp.ex:89: Hedwig.Transports.TCP.init/1
    (stdlib) gen_server.erl:306: :gen_server.init_it/6

I'm using the following versions of Elixir/Erlang (from iex --version):

Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.0.4

What am I doing wrong? (thanks ahead of time ❤️ ❤️ ❤️)

Support multiple configurations of the same adapter

For example --

config :eva, Eva.Robot,
  adapter: Hedwig.Adapters.Slack,
  name: "Ava",
  aka: "/",
  token: "",
  # for now, you can invite your bot to a channel in slack and it will join
  # automatically
  rooms: [],
  responders: [
    {Hedwig.Responders.Help, []},
    {Hedwig.Responders.GreatSuccess, []},
    {Hedwig.Responders.ShipIt, []}
  ]


config :eva, Eva.Robot,
  adapter: Hedwig.Adapters.Slack,
  name: "Eva",
  aka: "/",
    # fill in the appropriate API token for your bot
  token: "",
  # for now, you can invite your bot to a channel in slack and it will join
  # automatically
  rooms: [],
  responders: [
    {Hedwig.Responders.Help, []},
    {Hedwig.Responders.GreatSuccess, []},
    {Hedwig.Responders.ShipIt, []}
  ]

Send files/attachments

For a few days I've been prototyping a hedwig-based bot connected to our internal data mart. I've begun to get requests from folks for more complicated responses, and in some cases requests for file attachments.

Example of current situation

Jeff Weiss: @hedwig who's running what version?
Hedwig: /code
Customer         | Version
ACME Tech        | 3.7.1
Alpha, AB        | 3.8.0
...
Zeta Industries  | 3.8.3

This might work fine for a small-ish responses, but pretty quickly becomes unwieldy. And if the folks using hedwig responses are plugging into to something else, we have to compromise between readability and integration.

I'd like your thoughts on adding the ability for file transfer/attachments for responders. Maybe something like reply_with_file(msg, text, file) or send_with_file(msg, text, file)

Example ideal interaction

Jeff Weiss: @hedwig who's running what version?
Hedwig: @jeffweiss who's running which version is a fair bit of data, so here it is as csv
<customer_versions.csv>

Necessary Adapter Changes

Based on some preliminary research, I believe that each of the existing adapters (Slack, HipChat, and IRC) could implement this new functionality.

Non-Hedwig Options

I can use technologies outside of Hedwig to accomplish this, but it requires the user to incur a switching cost as there's now an additional layer between them and what they'd asked for.

  • Send email with attachment (assuming email of asker is known/discoverable)
  • Dropbox/Box
  • Pastebin/Gist

Thoughts?

Change Client.reply/2 to Client.send/2

I've been using Hedwig for my project lately, and I don't know how about the others but I find Client.reply/2 name pretty ambiguous, especially that it's something not related to Handler.reply/2. I guess adding new function that is called send instead of reply would be more readable and intuitive for newcomers.

Reply to IQ requests

According to RFC 3920:

An entity that receives an IQ request of type "get" or "set" MUST
reply with an IQ response of type "result" or "error" (the
response MUST preserve the 'id' attribute of the request).

I noticed this error when I enabled XEP-0199 in ejabberd. The server sends a number of ping IQs, but the client doesn't react. After multiple IQ stanzas have been left unanswered, the connection is closed.
I briefly looked at how SleekXMPP handles such IQ stanzas if the plugin for XEP-0199 is not explicitly activated. It responds with an error IQ to all unrecognized IQ stanzas:
https://github.com/fritzy/SleekXMPP/blob/4305eddb4f634803423cd53d193125a63b00769a/sleekxmpp/stanza/iq.py#L84

Changes to Hedwig.Message

Hey Hedwig adapter folks!

@jwarlander @bryanjos @jeffweiss

I've just pushed some changes to Hedwig based on some irritations I found with my implementation and I wanted to get things squared up before pulling the trigger on 1.0.

Here are the changes: 18273f1

All you'll need to do for your adapters is remove the adapter key from the %Hedwig.Message{} struct. The rest should be transparent.

If you have any other feedback...I would love to hear it!

[Feature Request] Add a catch all field to the Message Struct

The Flowdock API provides a way for messages to be posted to a particular thread, which is one of the defining features of the product. As far as I can tell, there is no way in the Message struct to keep a reference to a message's thread so that replies can be appended to a thread as opposed to the main room. Is it possible to add a catch all field to the message which can just be a Map? This would provide a bucket to hold extra data which may not need to be used by all adapters.

EDIT: This is relevant to me because I am working on a Flowdock Adapter for Hedwig

Connect with TLS

I'm trying to connect to the GCM XMPP Connection Server [1] with Hedwig. They require a TLS connection and don't support STARTTLS [2]. I tried different combinations in the config but it won't connect.

[info]  Outgoing stanza: {:xmlstreamstart, "stream:stream", [{"to", "gcm.googleapis.com"}, {"version", "1.0"}, {"xml:lang", "en"}, {"xmlns", "jabber:client"}, {"xmlns:stream", "http://etherx.jabber.org/streams"}]}
[error] GenServer #PID<0.165.0> terminating
** (MatchError) no match of right hand side value: {:error, {'not well-formed (invalid token)', <<21, 3, 1, 0, 2, 2, 70>>}}
    (hedwig) lib/hedwig/transports/tcp.ex:158: Hedwig.Transports.TCP.handle_data/3
    (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {:tcp, #Port<0.5575>, <<21, 3, 1, 0, 2, 2, 70>>}
State: %Hedwig.Transports.TCP{client: #PID<0.162.0>, compress?: false, config: %{client: #PID<0.162.0>, ignore_from_self?: true, port: 5235, preferred_auth_mechanisms: ["PLAIN"], require_tls?: true, server: "gcm-xmpp.googleapis.com", transport: Hedwig.Transports.TCP, use_compression?: false, use_stream_management?: true}, parser: {:parser, "", []}, pid: #PID<0.164.0>, socket: #Port<0.5575>, ssl?: false, transport: Hedwig.Transports.TCP}

However, if I replace :gen_tcp with :ssl in the Hedwig.Transports.TCP module the connection is established without errors. Is there some settings to force such behavior in the config?

[1] https://developers.google.com/cloud-messaging/xmpp-server-ref
[2] https://developers.google.com/cloud-messaging/ccs

A single message can trigger multiple responses

Hi, thanks for Hedwig. I've learned a lot from it already :)

I have a bot that I want to respond to both overheard and direct messages of the same kind, but respond differently to direct messages. For example:

me: @a_friend tell me what you think!
bot: yes, do tell!
...
me @bot tell me what you think!
bot: @me LGTM <3

If I add both a hear and a respond handler that match the same message shape, then for direct messages, both handlers are triggered, which feels weird in the chat:

me @bot tell me what you think!
bot: yes, do tell!
bot: @me LGTM <3

Ideally, in this case, I'd like Hedwig to only execute the respond handler. Maybe it makes sense to execute the hear handler if no respond handler matches, but maybe it makes sense to only consider hear handlers for overheard messages.

I'd be happy to pursue this if it seems like a good change. Cheers!

Defining adapter-dependent actions

tl;dr I'd like to use Slack-specific features like ephemeral messages in my responders, and I'm not sure the best approach for it. Is there a place to talk about hedwig? IRC, Gitter, Slack?

The long version:

The Slack bot in question currently uses elixir-slack, which provides a few functions that aren't currently covered by Hedwig. For example, it knows how to set a channel topic, which we used for team status and quick updates.

There are also some Slack features it doesn't implement that would be extremely useful in our bot, such as reactjis, threaded messages, and ephemeral messages.

I've implemented all these in different ways, and could fire off a bunch of pull requests for Hedwig and hedwig-slack if they'd be welcome. However, I'm not clear on how to implement them on the Hedwig side, or (maybe this is getting into the weeds a bit) whether that even fits with the Hedwig philosophy.

Suppose I want to respond to certain keywords by starting or joining a thread. Ideally, I want an ephemeral_reply helper function on the Hedwig.Responder module, and that would pass it along to the adapter. Easy peasy. But the concept of an ephemeral message doesn't exist in other adapters.

It seems like in some way, Hedwig needs to know about the existence of these kinds of actions at least enough to test them, and ideally implement them in the shared Responder helpers, but that opens a whole error handling can of worms...

Double supervision

There exists a subtle issue with Hedwig robot's because of how they are started.

The start_link function will start the robot under hedwig's supervisor. At the same time it's recommended to put the robot in the user's supervision tree. This causes the robot process to be supervised by two supervisors, which can lead to errors during code upgrade. There are no errors when the robot is restarted, only because it's running as a transient process under the hedwig supervisor.

The appropriate fix would be to not attach the process under the hedwig supervisor and leave the supervision to the user's application.

Make Robot a real OTP behaviour

Currently the Hewdig.Robot module masquerades as a pseudo behaviour through macros and injection of code into the user's module. This has couple issues, most notably the robot cannot be started twice - for example with different adapters - to handle traffic from two different networks. This limitation prevents me from using the library in it's current shape.

The Hedwig.Robot should be made a real behaviour with API similar to GenServer or to the custom Connection behaviour. This would make the code more flexible for runtime changes and configuration. Limiting injecting code into user modules should also speed up compilation. It is generally discouraged to inject large amounts of code into user modules.

If you agree this would be beneficial I would be glad to work on this.

Reconnect does not work

Hey,

I am using Hedwig with the IRC adapter on a Slack channel. I noticed that Slack sometimes sends a :disconnected message which is unhandled. So I extended the IRC adapter with the following function:

  def handle_info(:disconnected, state = {robot, _opts, _client}) do
    Hedwig.Robot.handle_disconnect(robot, nil)
    {:noreply, state}
  end

And my robot module contains:

  def handle_disconnect(_reason, state) do
    IO.puts "handle disconnect"
    {:reconnect, 5000, state}
  end

So when I start the bot with a slightly wrong configuration (so Slack disconnects me right away) I get the following:

12:27:31.190 [debug] Unknown message: {:unrecognized, "004", %IrcMessage{args: ["phillipp", "IRC-SLACK gateway"], cmd: "004", ctcp: false, host: [], nick: [], server: "irc.tinyspeck.com", user: []}}

12:27:36.183 [debug] Unknown message: {:received, "SSL connection required", %ExIrc.SenderInfo{host: [], nick: "slackbot", user: []}}

12:27:36.184 [debug] Unknown message: {:unrecognized, "444", %IrcMessage{args: ["phillipp", "SSL connection required"], cmd: "444", ctcp: false, host: [], nick: [], server: [], user: []}}
handle disconnect

12:27:36.190 [info]  'Connection to elixir-lang.irc.slack.com:6667 closed!'

As you can see, my handle_disconnect/2 function gets called but the bot itself does not reconnect.

Ensure stanzas other than Presence, Message, and IQ don't crash the client.

When a client is signed in with a given resource and you attempt to connect another instance with the same resource, you end up with a resource conflict. You will receive the following error stanza:

<stream:error>
  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams'></conflict>
  <text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'>
    Replaced by new connection
  </text>
</stream:error>

This will crash the client.

Error in process <0.9905.0> on node '[email protected]' with exit value: {badarg,[{'Elixir.Hedwig.Client',handle_stanza,2,[{file,
"lib/hedwig/client.ex"},{line,77}]},{'Elixir.Hedwig.Conn',await,1,[{file,"lib/hedwig/conn.ex"},{line,137}]}]}

Should probably just ignore when a record gets passed to handle_stanza/2:

def handle_stanza(_pid, stanza) when Record.is_record(stanza) do
  Logger.warn fn -> "Unhandled stanza: #{inspect stanza}" end
end

Can't compile responder when there are chinese characters in regular expression

defmodule Bot.Responders.Hello do
  use Hedwig.Responder

  respond ~r/你好嗎/iu, msg do
    emote msg, "Hi there!"
  end
end
== Compilation error on file lib/bot/responders/hello.ex ==
** (ArgumentError) argument error
    :erlang.binary_to_atom("__你好嗎__", :utf8)
    lib/hedwig/responder.ex:176: Hedwig.Responder.regex_to_name/1
    lib/hedwig/responder.ex:165: Hedwig.Responder."MACRO-respond"/5
    expanding macro: Hedwig.Responder.respond/3
    lib/bot/responders/hello.ex:13: Bot.Responders.Hello (module)

"connected" method/callback in handler

It would be nice to have the capability in a Handler to be informed after the connection is established. For example messages that are send via Hedwig.Client.reply(client, msg),before the client is connected, seem not to be delivered.

I created a Flowdock adapter

Hey everyone, I built a Flowdock adapter which you can see here which is still very much WIP, but is working. Im interested in getting it moved over to the hedwig-im organization so that it can be found more easily! What can I do to help out?

Also, thanks to @jeffweiss for encouraging me to make an adapter after his presentation at the Erlang user group in Seattle.

the robot does not seem to react to messages sent by himself

I'm not exactly sure why, but I'd like this to implement the !! operator.

> gif me party hard
> http://giphy.net/funny-gif
> @alfred !!
> gif me party hard
> http://giphy.net/another-funny-gif

Currently, the bot sends the message, it is displayed, and the logs show it has arrived.
But the bot does not react. It seems to ignore himself.

Any ideas?

More verbous error on connection failure

Right now when hedwig fails to connect there is simpy no report about failure it just keeps silent. I guess it'd be nice to know about disconnection and it's causes

undefined function Alfred.Robot.send/2

Hi

I have followed the intro and created Alfred.Robot and I'm trying to get the bot to send messages from an OTP app.

Even though I can locate the bot using Hedwig.whereis("alfred"), it keeps crashing with the following error:

20:50:32.849 [error] Process #PID<0.183.0> raised an exception
** (UndefinedFunctionError) undefined function Alfred.Robot.send/2
    (combover) AlfredRobot.send(#PID<0.161.0>, %Hedwig.Message{matches: nil, private: %{}, ref: nil, robot: nil, room: "myroom", text: "https://twitter.com/statuses/737733017519706112", type: "message", user: %{}})
    (elixir) lib/stream.ex:454: anonymous fn/4 in Stream.map/2
    (elixir) lib/stream.ex:1101: Stream.do_resource/5
    (elixir) lib/stream.ex:1247: Enumerable.Stream.do_each/4
    (elixir) lib/stream.ex:494: Stream.run/1

I'm not sure what I am missing??

The send function should be defined?

Outdated Readme

Hedwig.start_client(x)
** (UndefinedFunctionError) undefined function: Hedwig.start_client/1
    (hedwig) Hedwig.start_client(%{})

Document how to create ExUnit tests for a responder

I'm currently having problems to create tests for my user-land responders.
I tried to copy Hedwig.Responders.HelpTest:

working responder example:

defmodule Edgar.Responder.ItIsSomething do

  @moduledoc """
  react to it's something
  """

  use Hedwig.Responder

  @usage """
  <text> (it's something) - react to this text with an image
  """
  hear ~r/it's something/i, msg do
    send msg, "http://i3.kym-cdn.com/photos/images/original/000/114/139/tumblr_lgedv2Vtt21qf4x93o1_40020110725-22047-38imqt.jpg"
  end
end

test

defmodule Edgar.Responder.ItIsSomethingTest do
  use Hedwig.RobotCase

  @tag start_robot: true, name: "edgar"
  test "it's something - replies with an image url", %{adapter: adapter, msg: msg} do
    send adapter, {:message, %{msg | text: "it's something"}}
    assert_receive {:message, %{text: text}}
    assert String.equals(text, "http://i3.kym-cdn.com/photos/images/original/000/114/139/tumblr_lgedv2Vtt21qf4x93o1_40020110725-22047-38imqt.jpg")
  end
end

result


  1) test it's something - replies with an image url (Edgar.Responder.ItIsSomethingTest)
     test/edgar/responder/it_is_something.exs:5
     No message matching {:message, %{text: text}} after 100ms.
     The process mailbox is empty.
     stacktrace:
       test/edgar/responder/it_is_something.exs:7

Hope my question is not too dumb :)
Cheers o/

Hedwig.whereis/1 not working

A bot started with Hedwig.start_robot/2 or through the supervision tree doesn't register as a named process.

If you try calling Hedwig/which_robots/0, you get a return that looks like:

[{:undefined, #PID<0.193.0>, :worker, [Hedwig.Robot]}]

enter responder macro

hubot has enter and leave methods that can be very helpful.

well, at least the enter one is.

any plans on adding enter responder macro?

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.