Giter Site home page Giter Site logo

slackapi / bolt-python Goto Github PK

View Code? Open in Web Editor NEW
1.0K 25.0 236.0 5.27 MB

A framework to build Slack apps using Python

Home Page: https://slack.dev/bolt-python/

License: MIT License

Python 99.78% Shell 0.22%
slack slack-api bolt web-api chatops chatbot python pypi-url aiohttp flask django fastapi docker asyncio socket-mode websocket websockets websocket-client aiohttp-client

bolt-python's People

Contributors

angrychimp avatar dependabot[bot] avatar filmaj avatar handyman5 avatar hirosassa avatar jlujan-invitae avatar johtani avatar korymath avatar misscoded avatar mwbrooks avatar naveensan1 avatar nickovs avatar ntarora avatar objectfox avatar pdontha avatar rhnsharma avatar sarayourfriend avatar seratch avatar shoryu-n avatar siegerts avatar smyja avatar srajiang avatar stevengill avatar tattee avatar torifukukaiou avatar vgnshiyer avatar vokey avatar williambergamin avatar wongjas avatar ysaxon 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  avatar

bolt-python's Issues

Async support

  • App(async=True)
  • Samples with the following frameworks:
    • aiohttp
    • Tornado
    • FastAPI (async)
    • Sanic
    • Vibora

Add context.matches support in app.message handlers

Description

As with Bolt for jS, the app.message handler should provide context.matches to listener functions.

app.message(/^(hi|hello|hey).*/, async ({ context, say }) => {
  // RegExp matches are inside of context.matches
  const greeting = context.matches[0];

  await say(`${greeting}, how are you?`);
});

https://github.com/slackapi/bolt-js/blob/%40slack/bolt%402.2.3/docs/_basic/listening_messages.md#using-a-regexp-pattern

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue here.

TypeError: __call__() got an unexpected keyword argument 'ack' in samples/aws_chalice/simple_app.py

Bug Report

I got an error "TypeError: __call__() got an unexpected keyword argument 'ack'" when I deployed samples/aws_chalice/simple_app.py to Chalice.

Reproducible in:

The slack_bolt version

slack-bolt==0.2.1a0
slack-sdk==3.0.0a3

Python runtime version

Python 3.7.7

OS info

ProductName:	Mac OS X
ProductVersion:	10.15.6
BuildVersion:	19G73
Darwin Kernel Version 19.6.0: Sun Jul  5 00:43:10 PDT 2020; root:xnu-6153.141.1~9/RELEASE_X86_64

Steps to reproduce:

(Share the commands to run, source code, and project settings (e.g., setup.py))

cd samples/aws_chalice
cp -p .chalice/config.json.simple .chalice/config.json
# edit .chalice/config.json to change environment variables.
cp -p app.py app.py.org
cp -p simple_app.py app.py
sh deploy.sh

Expected result:

deploy.sh should finish successfully.

If I commented out these 3 lines it finished without error.

# bolt_app.command("/hello-bolt-python-chalice")(
#     ack=respond_to_slack_within_3_seconds, subsequent=[can_be_long],
# )

Actual result:

Note: I copied simple_app.py to app.py.

Traceback (most recent call last):
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/cli/__init__.py", line 636, in main
    return cli(obj={})
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/cli/__init__.py", line 206, in deploy
    deployed_values = d.deploy(config, chalice_stage_name=stage)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/deploy/deployer.py", line 354, in deploy
    return self._deploy(config, chalice_stage_name)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/deploy/deployer.py", line 360, in _deploy
    self._validate_config(config)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/deploy/deployer.py", line 383, in _validate_config
    validate_configuration(config)
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/deploy/validate.py", line 40, in validate_configuration
    routes = config.chalice_app.routes
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/config.py", line 141, in chalice_app
    app = v()
  File "/Users/ota/Documents/python/bolt-python/venv/lib/python3.7/site-packages/chalice/cli/factory.py", line 275, in load_chalice_app
    app = importlib.import_module('app')
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/Users/ota/Documents/python/bolt-python/samples/aws_chalice/app.py", line 39, in <module>
    ack=respond_to_slack_within_3_seconds, subsequent=[can_be_long],
TypeError: __call__() got an unexpected keyword argument 'ack'

Requirements (place an x in each of the [ ])

  • This is a bug specific to this SDK project.
  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue here.

Use "type": "block_actions" when "type" is absent and "action_id" exists in App.action listener constraints

Currently, some patterns supported by Bolt for JS are not yet available with Bolt for Python. For example, Bolt for Python requires "type": "block_actions" in the constraint object in an equivalent code to the following JS code. Also, it doesn't support filtering by a given block_id in the case yet.

https://github.com/slackapi/bolt-js/blob/0b37456f4781bf9262a36a6d200732840a156903/docs/_basic/listening_actions.md#listening-to-actions-using-a-constraint-object

// Your middleware will only be called when the action_id matches 'select_user' AND the block_id matches 'assign_ticket'
app.action({ action_id: 'select_user', block_id: 'assign_ticket' },
  async ({ action, ack, context }) => {
    await ack();
    try {
      const result = await app.client.reactions.add({
        token: context.botToken,
        name: 'white_check_mark',
        timestamp: action.ts,
        channel: action.channel.id
      });
    }
    catch (error) {
      console.error(error);
    }
  });

Reproducible in:

The slack_bolt version

v0.4.0a0

Python runtime version

any

OS info

any

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

event subscription url verification failed

When I add my ngrok url in api.slack.com for events subscription, the verification fails. Error:

DEBUG:slack_bolt.App:Applying slack_bolt.middleware.ssl_check.SslCheck
DEBUG:slack_bolt.App:Applying slack_bolt.middleware.request_verification.RequestVerification
DEBUG:slack_bolt.App:Applying slack_bolt.middleware.authorization.SingleTeamAuthorization
DEBUG:slack_bolt.App:Applying slack_bolt.middleware.ignoring_self_events.IgnoringSelfEvents
WARNING:slack_bolt.App:Unhandled request ({'token': '.....', 'challenge': '...', 'type': 'url_verification'})
127.0.0.1 - - [26/Jun/2020 12:55:41] "POST /slack/events HTTP/1.1" 404 -

Higher test coverage (particularly for adapters)

Currently, the test coverage of this project is around 78%. Before releasing the first-ever beta version, the percentage should be higher than 80%. The major reason for the law coverage is slack_bolt.adapter modules. We can add more tests for them.

https://codecov.io/gh/slackapi/bolt-python/tree/main/slack_bolt

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

payload != body in middleware/listener args

Currently, payload in middleware/listener args is an alias to body. This is not compatible with Bolt JS.

Reproducible in:

@app.action("action-id")
def handle_action(body, payload, action, ack):
    assert body != payload
    assert payload == action
    ack()

The slack_bolt version

The latest revision

Python runtime version

Any

OS info

Any

Steps to reproduce:

Run the above snippet in your Bolt app.

Expected result:

200 OK

Actual result:

500 Internal Server Error

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Migrate Docs off jekyll to sphinx?

Hi, let me first thank for an amazing port of slack bolt JS to python!

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Problem Description

There are so many documentation engines such as Jekyll, MKdocs but Sphinx prevails among all of them mainly for the readability of the result and plugins (I know others have plugins too but are nowhere near as mature as Sphinx).

I really like the colour scheme picked for the docs it reads very well with my dyslexia, however there are few issues I've ran into during reading.

  1. I've got lost in navigation during reading thinking that I'm still in section Basic Concepts and that the links below should point to the separate page not just anchor. It would be nice if the sub-sections highlighting reflected the position on the page if the intention is to provide SPA feel.

image

  1. This is where Sphinx shines, there are a lot of mentions about using middleware like ack, say, respond... etc but I had to reach out to the reading the code to actually find out which middleware a is available when.

Sphinx with apidoc is able to generate API documentation of the module which makes a navigation and learning the module much easier combined with the page search function makes the module learning curve much friendlier.

  1. On top of it Sphinx supports current MarkDown and also my beloved rst.

Let me know, what you think about it.

Jan

Lazy listener functions on Google Cloud Functions

As of version 0.3.0a0, we don't support lazy listener functions on Google Cloud Functions (as I mentioned in #45). We may explore solutions for it if many developers request the feature.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Solution for the use cases behind firewalls is needed

Problem

Inability to easily identifying requests coming from slack

Motivation

As slack is used in many work environments with strict security policies, it is a strong requisite to only expose the server/function to known hosts/ips.

Enhancement Proposal

It may result useful to provide an utility function or perhaps document how we can validate that the request comes indeed from slack. This would allow to open server's to certain IPs or lambdas to only trigger under certain conditions.
This would greatly reduce the attack surface on slack apps integrations.

Workaround

Currently, the promoted way of validating requests authenticity is by validating it against signing secret of the app. This is really useful, but it can't be used as a firewall rule, so it may be a prohibitive constraint for many possible integrations

Lazy Listener Functions

This is a proposal for a new feature, "Lazy Listener Functions" in Bolt for Python. This is a new feature for advanced developers that want to build serious Slack apps using Function as a Service. Adding this feature doesn't prevent most developers from using Bolt as with Bolt JS and Bolt Java. If this feature is greatly successful for many users, we may implement the same concept in other Bolt frameworks in the future.

Motivation

The primary motivation of this feature is to realize a more handy way to run asynchronous code after acknowledging incoming requests from Slack within 3 seconds.

When I was thinking about possible solutions for FaaS support in Bolt for JS, I came up with a "two-phase app" concept.

However, I think it was not simple enough, and the concept was far different from the original Bolt framework.

Lazy listener functions are more powerful and simpler than that approach. This mechanism enables developers to more easily manage async operations, even in FaaS environments.

API Design

Let's say you have the following slash command listener. Of course, this works without any issues.

from slack_bolt import App
app = App()

command = "/start-long-task"

@app.command(command)
def do_everything_here(payload, ack, respond, client):
    if payload.get("text", None) is None:
        ack(f":x: Usage: {command} (task name here)")
    else:
        task_name = payload["text"]
        ack(f"Accepted! (task: {task_name})") # This immediately sends 200 OK to Slack

        # start a long task
        time.sleep(5) # Doing something meaningful takes time
        task_name = payload["text"]
        respond(f"Completed! (task: {task_name}, url: https://example.com/result)")

With Lazy listener functions, the same code can be as below. All you need to do is give two types of args, ack and lazy, to a listener. By using this interface, you can separate the acknowledgment phase and long operation in a concise way.

def respond_to_slack_within_3_seconds(payload, ack):
    if payload.get("text", None) is None:
        ack(f":x: Usage: {command} (task name here)")
    else:
        task_name = payload["text"]
        ack(f"Accepted! (task: {task_name})")

def run_backend_operation(respond, payload):
    time.sleep(5) # Doing something meaningful takes time
    task_name = payload["text"]
    respond(f"Completed! (task: {task_name}, url: https://example.com/result)")

app.command(command)(
    ack=respond_to_slack_within_3_seconds,
    lazy=[run_backend_operation]
)

ack is responsible for returning an immediate HTTP response to Slack API servers within 3 seconds. By contrast, lazy functions are not supposed to return any response. They can do anything by leveraging all the listener args apart from ack() utility. Also, they are completely free from 3-second timeouts.

As the lazy is a list, you can set multiple lazy functions to a single listener. The lazy functions will be executed in parallel.

def acknowledge_anyway(ack):
    ack()

def open_modal(payload, client):
    api_response = client.views_open(trigger_id=payload["trigger_id"], view={ })

def start_background_job(payload):
    # do a long task
    pass

app.command("/data-request")(
    ack=acknowledge_anyway,
    lazy=[open_modal, start_background_job],
)

Mechanism

For complex apps, this mechanism can improve code readability. But the biggest benefit of this API is not readability. This mechanism provides better flexibility for choosing the runtime to execute asynchronous operations.

To support Lazy Listener Functions, the App/AsyncApp class should have a LazyListenerRunner internally. The component manages and runs lazy functions. Its start method kicks an async operation with the indication of recursive execution. The run method runs the lazy function with given payload and respond and say utilities.

class LazyListenerRunner:
    # Bolt runs this method when starting a new async operation
    # This method copies the payload and sets the lazy function's information to it.
    # The duplicated request has lazy_only: True and lazy_function_name: str.
    def start(self, lazy_function: Callable[..., None], request: BoltRequest) -> None:
        pass

    # This method can be executed in any of a different thread, Future, and a different runtime
    # In the case of AWS Lambda, this method is supposed to be invoked in a different Lambda container.
    def run(self, lazy_function: Callable[..., None], request: BoltRequest) -> None:
        pass

LazyListenerRunner submits a new async execution to its runtime. The runtime to use is fully customizable. It can be a different thread, asyncio's Future, different server/container, and whatever you prefer.

The out-of-the-box AWS Lambda adapter takes advantage of it.

class LambdaLazyListenerRunner(LazyListenerRunner):
    def __init__(self):
        self.lambda_client = boto3.client("lambda")

    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
        event: dict = request.context["lambda_request"] # duplicated original request
        headers = event["headers"]
        headers["x-slack-bolt-lazy-only"] = "1" # set lazy execution's specific header
        headers["x-slack-bolt-lazy-function-name"] = request.lazy_function_name # the function to run later
        event["method"] = "NONE"
        invocation = self.lambda_client.invoke(
            FunctionName=request.context["aws_lambda_function_name"],
            InvocationType="Event",
            Payload=json.dumps(event),
        )

Runners can rely on either of request.lazy_only / request.lazy_function_name (when sharing memory) or two x-slack-bolt- headers in recursive requests (when submitting a new request - these are converted to request.lazy_only and request.lazy_fucntion_name inside Bolt).

  • "x-slack-bolt-lazy-only" header value -> request.lazy_only: bool
  • "x-slack-bolt-lazy-function-name" header value -> request.lazy_function_name: Optional[str]

When Bolt runs App#dispatch (or AsyncApp#async_dispatch) method recursively, request.lazy_only should be True and request.lazy_function_name should exist. In the case, ack function and other lazy functions will be skipped.

Next Steps

I've already implemented the initial version of this feature. I will come up with a WIP (Work In Progress) pull request and am happy to hear feedback from other maintainers and the communities!

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue here.

Messages are received four times

Hello,

I have a similar error I had with the regular slack client : cf issue .

I switched to bolt in order to solve this issue and it was working for a couple of days but now I received the same message three to four times each time.

When I look at the logs, I have this for the same message:

{'client_msg_id': '59c755aa-80cf-4a77-8852-6073f776ca07', 'type': 'message', 'text': 'HPSM,.', 'user': 'U7PDK2W1J', 'ts': '1599757663.004700', 'team': 'T7P9RQX35', 'blocks': [{'type': 'rich_text', 'block_id': 'YhMu', 'elements': [{'type': 'rich_text_section', 'elements': [{'type': 'text', 'text': 'MESSAGE'}]}]}], 'channel': 'D8X4RT3PW', 'event_ts': '1599757663.004700', 'channel_type': 'im'}

{'client_msg_id': 'eece3ee7-2dab-4585-8bef-6be776645e80', 'type': 'message', 'text': 'HPSM,.', 'user': 'U7PDK2W1J', 'ts': '1599757626.004100', 'team': 'T7P9RQX35', 'blocks': [{'type': 'rich_text', 'block_id': 'oH4=p', 'elements': [{'type': 'rich_text_section', 'elements': [{'type': 'text', 'text': 'HPSM,.'}]}]}], 'channel': 'D8X4RT3PW', 'event_ts': '1599757626.004100', 'channel_type': 'im'}

(etc.)

And when I look at the IP sent, there are different.

Thank you in advance.

Add authorize function support

The authorize function that is available in Bolt JS is not yet supported in Bolt for Python.

# these arguments should be injected in correspondence with their names
# also, the Bolt framework provides `args` as a single value as with middleware/listeners
def authorize(enterprise_id, team_id, user_id):
    return {
        "bot_id": "B111",
        "bot_user_id": "W111",
        "bot_token": "xoxb-"
    }

app = App(
    signing_secret: os.environ["SLACK_SIGNING_SECRET"],
    authorize: authorize
)

For most use cases, probably, developers prefer using installation_store. That being said if a developer would like to implement a simple but more flexible way to fetch installation data, going with the authorize function can be a handy way.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Add middleware list for listener methods

Current:

    def shortcut(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: List[Callable[..., bool]] = []):
        def __call__(func):
            primary_matcher = builtin_matchers.shortcut(constraints)
            return self._register_listener(func, primary_matcher, matchers)

        return __call__

To-be:

    def shortcut(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: List[Callable[..., bool]] = [],
        middleware: List[Middleware] = []):
        def __call__(func):
            # TODO

        return __call__

Add app_id in context objects

As discussed at #126, we can consider providing an easier way to access app_id (a.k.a. api_app_id in some payloads) in context objects (BoltContext in Bolt for Python).

This needs to be implemented in Bolt for JS and Bolt for Java as well.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Add aliases for payload to align with Bolt for JS

Currently, Bolt for Python doesn't provide the following keyword arguments. This project should provide these keys to be compatible with Bolt for JS.

$ grep " this\['payload'\]" -R src/
src//types/options/index.ts:  body: this['payload'];
src//types/options/index.ts:  options: this['payload'];
src//types/shortcuts/index.ts:  shortcut: this['payload'];
src//types/shortcuts/index.ts:  body: this['payload'];
src//types/actions/index.ts:  action: this['payload'];
src//types/view/index.ts:  view: this['payload'];
src//types/command/index.ts:  command: this['payload'];
src//types/command/index.ts:  body: this['payload'];
src//types/events/index.ts:  event: this['payload'];
src//types/events/index.ts:  message: EventType extends 'message' ? this['payload'] : never;

https://github.com/slackapi/bolt-js/search?q=%22this%5B%27payload%27%5D%3B%22&unscoped_q=%22this%5B%27payload%27%5D%3B%22

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Add samples running on Google App Engine

Although running Python apps on Google App Engine is pretty straight-forward, having a minimal working example under the samples directory may be helpful for beginners.

The page URLs

  • samples/google_app_engine

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Support app_uninstalled, tokens_revoked events

One of the built-in Global Middleware (MultiTeamsAuthorization) calls the authorize function for each request. This, expectedly fails for app_uninstalled and tokens_revoked events but they should still be allowed to be processed for clean-up purposes (removing rows from DB, etc)

Reproducible in:

The slack_bolt version

slack-bolt==0.9.3b0
slack-sdk==3.0.0b0

Python runtime version

Python 3.9.0

OS info

ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H2
Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64

Steps to reproduce:

(Share the commands to run, source code, and project settings (e.g., setup.py))

  1. Install an App to a workspace using OAuth (for multi-team authorization)
  2. Subscribe to app_uninstalled or tokens_revoked events
  3. Create function to listen for either of those events
  4. Uninstall the App from the Manage Apps page

Expected result:

The middleware should allow passing to the next middleware even if the AuthorizeResult is None so that cleanup can be done when a user uninstalls the app

Actual result:

INFO 2020-10-21 15:57:14,508 basehttp 15742 123145406881792 "POST /slack/events HTTP/1.1" 200 52
DEBUG 2020-10-21 15:59:50,150 app 15742 123145406881792 Applying slack_bolt.middleware.ssl_check.ssl_check.SslCheck
DEBUG 2020-10-21 15:59:50,150 app 15742 123145406881792 Applying slack_bolt.middleware.request_verification.request_verification.RequestVerification
DEBUG 2020-10-21 15:59:50,150 app 15742 123145406881792 Applying slack_bolt.middleware.authorization.multi_teams_authorization.MultiTeamsAuthorization
DEBUG 2020-10-21 15:59:50,173 app 15742 123145423671296 Applying slack_bolt.middleware.ssl_check.ssl_check.SslCheck
DEBUG 2020-10-21 15:59:50,173 app 15742 123145423671296 Applying slack_bolt.middleware.request_verification.request_verification.RequestVerification
DEBUG 2020-10-21 15:59:50,173 app 15742 123145423671296 Applying slack_bolt.middleware.authorization.multi_teams_authorization.MultiTeamsAuthorization
DEBUG 2020-10-21 15:59:50,546 authorize 15742 123145406881792 The stored bot token for enterprise_id: None team_id: TJ4GH3ZRC is no longer valid. (response: {'ok': False, 'error': 'account_inactive'})
ERROR 2020-10-21 15:59:50,546 multi_teams_authorization 15742 123145406881792 auth.test API call result is unexpectedly None
DEBUG 2020-10-21 15:59:50,549 authorize 15742 123145423671296 The stored bot token for enterprise_id: None team_id: TJ4GH3ZRC is no longer valid. (response: {'ok': False, 'error': 'account_inactive'})
ERROR 2020-10-21 15:59:50,555 multi_teams_authorization 15742 123145423671296 auth.test API call result is unexpectedly None

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

app.action without type should work for more actions

In addition to #65, app.action should handle callback_id for dialog submissions. This is required for the compatibility with Bolt for JS.

#78 (comment)

Bolt for Python doesn't work with code for dialog submission yet (it works when passing a dict to app.action or use app.dialog_submission). I will create a ticket and will fix this.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Add reference page in documents

For developers that are not familiar with Bolt for JS, the kwargs injection in Bolt for Python may be a bit surprising. The document.

The interface is highly inspired by Bolt for JS and I agree this is not a common design in Python. The document should clearly mention this.

Considering the place to put this type of content, we may want to have FAQ section in the template. We can mention other common topics (e.g., how to use Bolt along with Flask, Django? how to install a valid version of Python runtime) there. Any thoughts? @shaydewael

The page URLs

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

"step" arg in listeners for workflow_step_execute events

My implementation was incomplete.

Reproducible in:

The slack_bolt version

The latest version still has this issue.

 $ pip freeze | grep slack
-e [email protected]:slackapi/bolt-python.git@6a8d38da5704be7d0d7124162057b3d71a77ed15#egg=slack_bolt
slack-sdk==3.0.0a9

Python runtime version

Any

OS info

Any

Steps to reproduce:

def execute(step, complete, fail):
    inputs = step["inputs"] # raises an Exception as the `step` is None

ws = WorkflowStep(
    callback_id="add_task",
    edit=edit,
    save=save,
    execute=execute,
)
app.step(ws)

Expected result:

step in workflow_step_execute listeners is a valid dict value.

Actual result:

step in workflow_step_execute listeners is None.

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

SlackRequestHandler does not work on Lambda

Problem

I was create app by bolt for python, And deploy on Lambda.
But It's not work correct.

I investigated this issue and found problems in determining the following areas.
https://github.com/slackapi/bolt-python/blob/main/slack_bolt/adapter/aws_lambda/handler.py
line:35
method = event.get("requestContext", {}).get("http", {}).get("method")

On my Lambda environment, it's always return None.

Workaround

I modified it below.
method = event.get("requestContext", {}).get("httpMethod", {})

It's work correct.

Add more tests and samples for dialogs

Currently, the ack(errors=errors) implementation only assumes view submission requests. I will add samples and tests to verify if it works with dialogs.

#78 (comment)

ack(errors=errors) should work for dialogs too. If it doesn't work for dialog, I will fix it.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Failed to find bot installation data

I'm using Bolt with Flask and I can't make it work. I'm using it with passenger and nginx. I've alread configured /slack/events but when I try to make the app react to app_home_opened I get this:

App 5912 output: [ pid=5912, time=2020-10-28 22:20:40,851 ]: Failed to find bot installation data for enterprise: none, team: T027KNX9B: [Errno 2] No such file or directory: './data/none-T027KNX9B/bot-latest'
App 5912 output: [ pid=5912, time=2020-10-28 22:20:40,851 ]: auth.test API call result is unexpectedly None

Reproducible in:

pip freeze | grep slack
python --version
sw_vers && uname -v # or `ver`

The slack_bolt version

slack-bolt==0.9.5b0
slack-sdk==3.0.0rc1

Python runtime version

Python 3.6.8

OS info

Ubuntu 18.04

Steps to reproduce:

App code:

import logging
import os
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore

logging.basicConfig(level=logging.DEBUG)

oauth_settings = OAuthSettings(
    client_id=os.environ["SLACK_CLIENT_ID"],
    client_secret=os.environ["SLACK_CLIENT_SECRET"],
    scopes=["channels:read", "groups:read", "chat:write"],
    redirect_uri=None,
    install_path="/slack/install",
    redirect_uri_path="/slack/oauth_redirect",
    state_store=FileOAuthStateStore(expiration_seconds=600, base_dir="./data")
)

app = App(
    signing_secret=os.environ["SLACK_SIGNING_SECRET"],
    installation_store=FileInstallationStore(base_dir="./data"),
    oauth_settings=oauth_settings
)

@app.middleware  # or app.use(log_request)
def log_request(logger, body, next):
    logger.debug(body)
    return next()


@app.event("app_mention")
def event_test(body, say, logger):
    logger.info(body)
    say("What's up?")


@app.event("message")
def handle_message():
    pass


from flask import Flask, request

BotApp = Flask(__name__)
handler = SlackRequestHandler(app)


@BotApp.route("/slack/events", methods=["POST"])
def slack_events():
    return handler.handle(request)


@BotApp.route("/slack/install", methods=["GET"])
def install():
    return handler.handle(request)


@BotApp.route("/slack/oauth_redirect", methods=["GET"])
def oauth_redirect():
    return handler.handle(request)


@app.event("app_home_opened")
def open_modal(client, event, logger):
  try:
    # view.publish is the method that your app uses to push a view to the Home tab
    client.views_publish(
      # the user that opened your app's app home
      user_id = event["user"],
      # the view object that appears in the app home
      view={
        "type": "home",
        "callback_id": "home_view",

        # body of the view
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Welcome to your _App's Home_* :tada:"
            }
          },
          {
            "type": "divider"
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "This button won't do much for now but you can set up a listener for it using the `actions()` method and passing its unique `action_id`. See an example in the `examples` folder within your Bolt app."
            }
          },
          {
            "type": "actions",
            "elements": [
              {
                "type": "button",
                "text": {
                  "type": "plain_text",
                  "text": "Click me!"
                }
              }
            ]
          }
        ]
      }
    )
  except Exception as e:
    logger.error(f"Error opening modal: {e}")

if __name__ == "__main__":
	BotApp.run()

Expected result:

app reacted to the event

Actual result:

It fails, I think somehow it's not saving the OAuth data but I don't know why

Improve the compatibility with Bolt JS in OAuth flow support

We are not aiming to completely imitate Bolt for JS in this library as Python programming has different customs and idiomatic ways. That said, the current implementation may be confusing for developers who are already familiar with Bolt for JS.

The simplest configuration is to use env variables and the built-in file based implementations (FileInstallationStore, FileOAuthStateStore).

from slack_bolt import App
app = App() # use file based store implementations

if __name__ == "__main__":
    app.start(3000)

# pip install slack_bolt
# export SLACK_SIGNING_SECRET=***
# export SLACK_CLIENT_ID=111.111
# export SLACK_CLIENT_SECRET=***
# export SLACK_SCOPES=app_mentions:read,channels:history,im:history,chat:write
# python app.py

This code internally initializes the following settings. The oauth_settings contains the options in InstallerOptions and InstallProviderOptions, and CallbackOptions in Bolt JS (and its underlying Node SDK). Some of the options are unsupported in Python yet but if many developers request to add them, we will add them in the future releases.

import os
from slack_bolt import App
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore

app = App(
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
    installation_store=FileInstallationStore(),
    oauth_settings=OAuthSettings(
        client_id=os.environ.get("SLACK_CLIENT_ID"),
        client_secret=os.environ.get("SLACK_CLIENT_SECRET"),
        scopes=["app_mentions:read","channels:history","im:history","chat:write"],
        user_scopes=[],
        redirect_uri=None,
        install_path="/slack/install",
        redirect_uri_path="/slack/oauth_redirect",
        state_store=FileOAuthStateStore(expiration_seconds=600)
    )
)

if __name__ == "__main__":
    app.start(3000)

# pip install slack_bolt
# export SLACK_SIGNING_SECRET=***
# export SLACK_CLIENT_ID=111.111
# export SLACK_CLIENT_SECRET=***
# python app.py

As bolt-python v0.4 hasn't supported CallbackOptions equivalent options, we will add the ways to register the callback functions in a similar way.

from slack_bolt import BoltResponse
from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs

def success(args: SuccessArgs) -> BoltResponse:
    return BoltResponse(status=200, body="Thanks!")

def failure(args: FailureArgs) -> BoltResponse:
    return BoltResponse(status=args.suggested_status_code, body=args.reason)

app = App(
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
    oauth_settings=OAuthSettings(
        callback_options=CallbackOptions(
            success=success,
            failure=failure
        )
    ) # in this case, other options are loaded from env variables
)

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

say() doesn't work with some events (e.g., "reaction_added")

say() method may fail due to the proper extraction of channel ID inside Bolt for Python. One example we detected is "reaction_added" but we should check if there are any other patterns. We can find all the patterns that should be supported in Bolt for JS's test code.

Reproducible in:

The slack_bolt version

version 0.4.0a0

Python runtime version

any

OS info

any

Steps to reproduce:

@app.event("reaction_added")
def foo(say):
    say("foo")
DEBUG:slack_bolt.App:Checking listener: foo ...
DEBUG:slack_bolt.App:Running listener: foo ...
ERROR:slack_bolt.App:say without channel_id here is unsupported
Traceback (most recent call last):
  File "/path-to-project/app/app.py", line 375, in run_ack_function_asynchronously
    listener.run_ack_function(request=request, response=response)
  File "/path-to-project/listener/custom_listener.py", line 46, in run_ack_function
    return self.ack_function(
  File "samples/app.py", line 31, in foo
    say("foo")
  File "/path-to-project/context/say/say.py", line 52, in __call__
    raise ValueError("say without channel_id here is unsupported")
ValueError: say without channel_id here is unsupported

Expected result:

The app can post a message in the channel.

Actual result:

ValueError shared above.

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Add global error handler

Description

As with other Bolt frameworks, the error handler should be customizable.

# TODO: error handler
self._framework_logger.exception(
f"Failed to run listener function (error: {e})"
)

# TODO: error handler
self._framework_logger.exception(
f"Failed to run listener function (error: {e})"
)

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter

Requirements (place an x in each of the [ ])

  • [x I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue here.

Examples for sending messages outside of the response flow

There is some information that indicates to use the WebClient that is part of the Bolt app however under most contexts the WebClient has been initialized with an empty token (understandably a reasonable situation).

What is unknown is the appropriate method to acquire a given installation (the xoxb-) token as well as initialize either a new WebClient or use the client attached to the Bolt application.

The page URLs

Requirements

Adding additional examples for sending a message that is out of the normal response flow would be great. A simplistic example would be if a user makes a payment on a website, a message would be sent to a given installation and channel indicating the event happened.

IgnoringSelfEvents filters member_joined_channel/member_left_channel events out

The global middleware IgnoringSelfEvents is enabled by default for instances of App.

The docs show an example for Listener middleware on how to filter out messages with bot_message subtype. Since a global middleware is already enabled for this, there doesn't appear to be a way to purposefully react to bot events.

Looking through the code, I don't see any reason that this middleware should be required so it shouldn't break things to turn it off but I am not sure how to do that.

The use case is for member_joined_channel events. The app I am working on lets users pick a channel for the bot to work in and then, as expected, the bot is unable to post to that channel until it is invited so I need to monitor for the bot being invited to the channel.

I configured a simple function (@app.event("member_left_channel")) to watch for these events but the aforementioned middleware prevents them from being executed.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

top-level constructor arg "authorize" is not respected when having "oauth_flow" together

(Filling out the following details about bugs will help us solve your issue sooner.)

Reproducible in:

The slack_bolt version

0.9.4b0

Python runtime version

any

OS info

any

Steps to reproduce:

app = App(
    oauth_flow=oauth_flow,
    authorize=my_authorize_func # this won't be respected
)

Expected result:

The argument is used or the framework should tell something if it has to ignore it.

Actual result:

The argument is not used.

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Add samples for Google Cloud Functions users

Description

As we already have samples for AWS Lambda users, we can put some code for Google Cloud Functions under samples.

References:

The page URLs

  • ./samples in this repository

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue here.

Test failure due to Chalice's breaking change

$ travis_retry pytest tests/adapter_tests/
============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-5.4.3, py-1.7.0, pluggy-0.12.0
rootdir: /home/travis/build/slackapi/bolt-python, inifile: pytest.ini
plugins: cov-2.10.1
collected 53 items
tests/adapter_tests/test_aws_chalice.py ....F.                           [ 11%]
tests/adapter_tests/test_aws_lambda.py ........                          [ 26%]
tests/adapter_tests/test_bottle.py ...                                   [ 32%]
tests/adapter_tests/test_bottle_oauth.py .                               [ 33%]
tests/adapter_tests/test_cherrypy.py ....                                [ 41%]
tests/adapter_tests/test_cherrypy_oauth.py ..                            [ 45%]
tests/adapter_tests/test_django.py ....                                  [ 52%]
tests/adapter_tests/test_falcon.py ....                                  [ 60%]
tests/adapter_tests/test_fastapi.py ....                                 [ 67%]
tests/adapter_tests/test_flask.py ....                                   [ 75%]
tests/adapter_tests/test_lambda_s3_oauth_flow.py .                       [ 77%]
tests/adapter_tests/test_pyramid.py ....                                 [ 84%]
tests/adapter_tests/test_starlette.py ....                               [ 92%]
tests/adapter_tests/test_tornado.py ...                                  [ 98%]
tests/adapter_tests/test_tornado_oauth.py .                              [100%]
=================================== FAILURES ===================================
______________________ TestAwsChalice.test_lazy_listeners ______________________
self = <tests.adapter_tests.test_aws_chalice.TestAwsChalice object at 0x7fcdd5cbcfd0>
    def test_lazy_listeners(self):
        app = App(client=self.web_client, signing_secret=self.signing_secret,)
    
        def command_handler(ack):
            ack()
    
        def say_it(say):
            say("Done!")
    
        app.command("/hello-world")(ack=command_handler, lazy=[say_it])
    
        input = (
            "token=verification_token"
            "&team_id=T111"
            "&team_domain=test-domain"
            "&channel_id=C111"
            "&channel_name=random"
            "&user_id=W111"
            "&user_name=primary-owner"
            "&command=%2Fhello-world"
            "&text=Hi"
            "&enterprise_id=E111"
            "&enterprise_name=Org+Name"
            "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx"
            "&trigger_id=111.111.xxx"
        )
        timestamp, body = str(int(time())), input
    
        chalice_app = Chalice(app_name="bolt-python-chalice")
        slack_handler = ChaliceSlackRequestHandler(app=app, chalice=chalice_app)
    
        headers = self.build_headers(timestamp, body)
        headers["x-slack-bolt-lazy-only"] = "1"
        headers["x-slack-bolt-lazy-function-name"] = "say_it"
    
        request: Request = Request(
            method="NONE",
            query_params={},
            uri_params={},
            context={},
            stage_vars=None,
            is_base64_encoded=False,
            body=body,
>           headers=headers,
        )
E       TypeError: __init__() got an unexpected keyword argument 'method'
tests/adapter_tests/test_aws_chalice.py:242: TypeError
=============================== warnings summary ===============================

Category (place an x in each of the [ ])

tests

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Events API's auto_acknowledgement may occasionally fail

It can be easily reproduced by the code shared at #75 (comment)

Reproducible in:

The slack_bolt version

$ pip freeze | grep slack
-e [email protected]:slackapi/bolt-python.git@6355714bb6917f39cc2cee96f21463bc3eac1d74#egg=slack_bolt
slack-sdk==3.0.0a4

Python runtime version

(env_3.8.5) $ python --version
Python 3.8.5

OS info

(env_3.8.5) $ sw_vers && uname -v
ProductName:	Mac OS X
ProductVersion:	10.15.6
BuildVersion:	19G2021
Darwin Kernel Version 19.6.0: Thu Jun 18 20:49:00 PDT 2020; root:xnu-6153.141.1~1/RELEASE_X86_64

Steps to reproduce:

    def test_stable_auto_ack(self):
        app = App(client=self.web_client, signing_secret=self.signing_secret)

        @app.event("reaction_added")
        def handle_app_mention():
            raise Exception("something wrong!")

        for _ in range(10):
            timestamp, body = (
                str(int(time())),
                json.dumps(self.valid_reaction_added_body),
            )
            request: BoltRequest = BoltRequest(
                body=body, headers=self.build_headers(timestamp, body)
            )
            response = app.dispatch(request)
            assert response.status == 200

Expected result:

Getting 200 OK all the times

Actual result:

Occasionally getting a 404 Not Found response

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

setup steps failing for me

Running the following setup steps fails on the last step for me.

python -m venv env
source env/bin/activate
pip install -U pip
pip install -U slackclient
pip install -U -i https://test.pypi.org/simple/ slack_bolt

failure:

pip install -U -i https://test.pypi.org/simple/ slack_bolt
Looking in indexes: https://test.pypi.org/simple/
Collecting slack_bolt
  Downloading https://test-files.pythonhosted.org/packages/b4/19/fd801d28cd2271098f0954cbc6b00199a1803bb4e56dfa4c694f941676e4/slack_bolt-0.1.0a1-py3-none-any.whl (28 kB)
ERROR: Could not find a version that satisfies the requirement slackclient==2.7.1 (from slack_bolt) (from versions: 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.6, 1.1.0, 1.1.1, 1.1.2, 1.1.3, 1.3.0, 1.3.1, 1.3.2, 2.0.0b1, 2.0.1)
ERROR: No matching distribution found for slackclient==2.7.1 (from slack_bolt)

Looks like i already have the latest slackclient

Requirement already up-to-date: slackclient in ./env/lib/python3.7/site-packages (2.7.2)

It is because of this line in setup.py

install_requires=[
        "slackclient==2.7.1", # TODO: will be replaced with slack_sdk==3.0.0
    ],

I'll send a fix in a sec here

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.