GithubHelp home page GithubHelp logo

joj0 / synadm Goto Github PK

View Code? Open in Web Editor NEW
178.0 7.0 25.0 578 KB

Command line admin tool for Synapse (the Matrix reference homeserver)

Home Page: https://synadm.readthedocs.io

License: GNU General Public License v3.0

Python 98.56% Shell 1.05% Makefile 0.38%
synapse matrix api-client python3

synadm's Introduction

synadm - the Matrix-Synapse admin CLI

About

A CLI tool to help admins of Matrix-Synapse homeservers conveniently issue commands available via its Admin API.

Prerequisites

  • Python 3.6+
  • a running Synapse instance
  • an admin-enabled user on the instance
  • the admin user's access token

synadm is designed to run either directly on the host running the Synapse instance or on a remote machine able to access Synapse's API port. Synapse's default Admin API endpoint address usually is http://localhost:8008/_synapse/admin or https://localhost:8448/_synapse/admin.

Installation

Install from PyPI

pip3 install synadm

Install from git

1. Check Python Version

python3 --version should show at least v3.6.x

2. Clone Repo:

git clone https://github.com/joj0/synadm

3. Install Package Globally

This will install synadm and all dependent Python packages to your system's global Python site-packages directory:

cd synadm
sudo pip install .

Note: If you get an import error for setuptools, make sure the package is installed. Debian based systems: sudo apt install python3-setuptools, RedHat based: sudo yum install python3-setuptools

4. Run

synadm should now run fine without having to add a path in front of it:

synadm -h

Note: Usually setuptools installs a command wrapper to /usr/local/bin/synadm, but that depends on your system.

Note: In case you don't want synadm to be installed to a global system directory, you can find an alternative way of installing in the contribution docs.

Note: synadm is multi-user aware - it stores its configuration inside the executing user's home directory. See chapter configuration.

Configuration

Getting an Admin Token

To find out your admin user's token in Element-Web: Login as this user - "Click User Avatar" - "All Settings" - "Help & About" - Scroll down - "Advanced" - "Access Token"

Or use synadm to fetch a token already. Use the fully qualified Matrix ID of the admin user:

synadm matrix login @admin_username:yourdomain.org
Password:

If you issue this command in a fresh synadm installation, the configurator will launch anyway.

  • Answer the questions.
  • Set token to "invalid" at first, to convience synadm to launch the matrix login command (otherwise you'd get a "Configuration incomplete" error).
  • After successfully entering your admin password you will be presented a token which you can finally set by re-launching the configurator as described below.

The configurator

synadm asks for necessary configuration items on first launch automatically. Also whenever new mandatory configuration items where added (eg after an update), the user will be prompted for missing items automatically.

Configuration can be changed any time by launching the configurator directly:

synadm config

Configuration will be saved in ~/.config/synadm.yaml

Note: Be aware that once you configured synadm, your admin user's token is saved in the configuration file. On Posix compatible systems permissions are set to mode 0600, on other OS's it is your responsibilty to change permissions accordingly.

matrix-docker-ansible-deploy

To use synadm with Synapse homeservers that were installed using matrix-docker-ansible-deploy you have two options.

Access the Synapse Admin API's "via the public endpoint" similar to a Matrix client.

  • In vars.yaml set matrix_nginx_proxy_proxy_matrix_client_api_forwarded_location_synapse_admin_api_enabled: true.
  • The API's are accessible on the Client-Server API port, at https://matrix.DOMAIN.
  • Install synadm on your Docker host or on a separate machine.
  • Configure synadm to access at https://matrix.DOMAIN:443/_synapse/admin

Alternatively, you can access the API's on the container network matrix.

  • Synapse is accessible via the hostname matrix-synapse resolved by the internal Docker DNS server.
  • The containers are connected internally via a network named matrix by default.
  • Start a container on that same network and install synadm into it.
  • Configure synadm to access at http://matrix-synapse:8008/_synapse/admin (http here, not https).

Find some more details about the topic in this issue post on the matrix-docker-ansible-deploy repo.

Note that currently synadm is using a part of the Server-Server (Federation) API (keys/v2/server) to retrieve "its own homeserver name". This affects some of the media management commands. By default and also as the Matrix spec recommends, this API is not accessible via the Client-Server API port. We are working on a better solution to retrieve the own servername but as a workaround the key API's can be exposed by setting matrix_synapse_http_listener_resource_names: ["client","keys"] in vars.yaml.

Find more details about the topic here.

Usage

Use the online help of the main command:

synadm -h

and of the available subcommands:

synadm version -h
synadm user -h
synadm room -h

You even can spare the -h option, synadm will show some abbreviated help for the executed subcommand anyway. For example:

synadm user

or

synadm user details

will show essential help for the particular subcommand right away.

Note: A list of currently available commands is found in chapter implementation status / commands list as well as in the following chapter.

Command Line Reference

A detailed Command Line Reference can be found in synadm's readthedocs documentation.

Advanced Usage

Examples of how synadm can be used in shell scripts and oneliners is provided in the Scripting Examples docs chapter.

Update

Update PyPI Package

pip3 install synadm --upgrade

Update git Installation

To update synadm to the latest development state, just update your git repo and reinstall:

cd synadm
git pull
pip install .

Note: If you installed it to a Python venv, activate it.

Note: If you installed it in editable mode (or for development), you can spare the pip install . command - just git pull and you're done.

Implementation Status / Commands List

Follow this link to the official Synapse Admin API docs - direct links to the specific API documentation pages are provided in the list below.

Note: Most commands have several optional arguments available. Put -h after any of the below listed commands to view them or have a look at the Command Line Reference.

  • Account Validity
  • Delete Group (delete community)
  • Event Reports
  • Media Admin
    • media list -r <room id>
    • media list -u <user id> (alias of user media <user id>)
    • media quarantine -s <server name> -i <media id>
    • media quarantine -r <room id>
    • media quarantine -u <room id>
    • media protect <media id>
    • media delete -s <server name> -i <media id>
    • media delete -s <server name> --before <date> --size 1024
    • media purge --before <date> (purge remote media API)
  • Purge History
    • history purge <room id>
    • history purge-status <purge id>
  • Purge Rooms (DEPRECATED, covered by room delete)
  • Register Users
  • Manipulate Room Membership
    • room join
  • Rooms
    • room list
    • room details <room id>
    • room members <room id>
    • room delete <room id>
    • room make-admin <room id> <user id>
    • room state <room id>
    • Additional commands and aliases around room management
      • room search <search-term> (alias of room list -n <search-term>)
      • room resolve <room alias>
      • room power-levels
      • room block
      • room block-status
  • Server Notices
  • Shutdown Room (DEPRECATED, covered by room delete)
  • Statistics
    • synadm media user-stats
    • synadm room largest
  • Users
    • user details <user id>
    • user modify <user id> (also used for user creation)
    • user list
    • user deactivate <user id> (including GDPR erase)
    • user password <user id>
    • user membership <user id>
    • user whois <user id>
    • user shadow-ban <user id>
    • user media -u <user id> (also available as media list -u <user id>)
    • user login <user id>
    • Additional commands and aliases around user management
      • user search <search-term> (shortcut to user list -d -g -n <search-term>)
      • user create <user id> (alias of user modify ...)
      • user prune-devices <user id>
  • Server Version
    • version
  • Registration Tokens
    • regtok list
    • regtok details <registration token>
    • regtok new
    • regtok update <registration token>
    • regtok delete <registration token>

Get in Touch

If you need advice on using synadm, have a feature idea or would like to discuss anything else around synadm, get in touch via Matrix!

We are hanging around in the official support room for Synapse, #synapse:matrix.org. Usually you'll find synadm users there that might answer your questions already. If not, mentioning synadm will ping us with the help of Element's keyword notify feature and we'll try to get in touch.

The most direct way to reach synadm maintainers as well as seasoned users and Synapse admins is by joining #synadm:peek-a-boo.at.

If you are sure you've found a bug that was not already reported, certainly directly opening an issue on GitHub is a valid option too. If unsure, ask in #synadm:peek-a-boo.at first.

Contributing

First of all, thanks for your interest in contributing to synadm! We appreciate any help, no matter if you are a programmer or a user. Both groups can do valuable things for the synadm project. We love providing a useful tool to fellow Synapse sysadmins but rely on contribution from the Synapse and Matrix community to keep synadm useful, current and stable.

Please review the contributing docs for guidelines and help around the topic!

synadm's People

Contributors

aaronraimist avatar andir avatar ascurius avatar ashfame avatar friskygote avatar gergelypolonkai avatar govynnus avatar heurtematte avatar hpdeifel avatar jacksonchen666 avatar joj0 avatar kaiyou avatar lykos153 avatar maclemon avatar n-peugnet avatar nemobis avatar neunenak avatar rht avatar schwindp avatar waclaw66 avatar xiretza 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

synadm's Issues

default output format not working

On first start it asks for a default output format (I put json) but subsequently it always outputs in yaml mode. Manually specifying the output format works correctly.

list index errors on pagination with --from <number>

when paginating through results and --from number is too high index error should be catched:

jojo@jum ~/git/synadm (media) $ synadm -o human media list -u @testuser1:peek-a-boo.at -f 1019
User has uploaded 1019 media blobs.
Traceback (most recent call last):
  File "/usr/local/bin/synadm", line 11, in <module>
    load_entry_point('synadm', 'console_scripts', 'synadm')()
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/jojo/git/synadm/synadm/cli/media.py", line 83, in media_list_cmd
    from_=from_, limit=limit, sort=sort, reverse=reverse)
  File "/usr/local/lib/python3.6/dist-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/home/jojo/git/synadm/synadm/cli/user.py", line 396, in user_media_cmd
    helper.output(media["media"])
  File "/home/jojo/git/synadm/synadm/cli/__init__.py", line 172, in output
    click.echo(self.formatter(data))
  File "/home/jojo/git/synadm/synadm/cli/__init__.py", line 39, in humanize
    if isinstance(data, list) and isinstance(data[0], dict):
IndexError: list index out of range

applies to commands: media list, user list, room list and probably more....FIXME

regtok delete is broken

I'm using 0.41.2 (git clone of the repo). Running synadm regtok delete <token> results in:

Traceback (most recent call last):
  File "/root/code/synadm/.venv/bin/synadm", line 33, in <module>
    sys.exit(load_entry_point('synadm', 'console_scripts', 'synadm')())
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/root/code/synadm/.venv/lib/python3.10/site-packages/click/decorators.py", line 38, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/root/code/synadm/synadm/cli/regtok.py", line 146, in regtok_delete
    response = helper.api.regtok_delete(token)
  File "/root/code/synadm/synadm/api.py", line 1261, in regtok_delete
    return self.query("delete", "v1/registration_tokens/{token}",
  File "/root/code/synadm/synadm/api.py", line 114, in query
    urlpart = urlpart.format(*args, **kwargs)
KeyError: 'token'

The rest of the commands seem to work.

Make timeout value configurable by user

When making queries very often I got the timeout error:

synadm room details '!room_id:ru-matrix.org'
ERROR Timeout: HTTPConnectionPool(host='localhost', port=8008): Read timed out. (read timeout=7)

Seems this is because my server is not so nimble as expected, but it want some love too from this script :)

At now timeout is hard-coded in tgose lines:

resp = requests.get(url, headers=self.headers, timeout=7)

resp = requests.post(url, headers=self.headers, timeout=7, data=post_data)

resp = requests.put(url, headers=self.headers, timeout=7, data=put_data)

So will be good to make timeout configurable via .config/synadm.yaml file.

Integrate code-style checks into our CI

I'd like to introduce some code-style checks into the CI. I have learnt by contributing to github.com/beetbox/beets what a valuable feature this can be. They check using flake8 via tox, so this might not be the right approach for us: https://github.com/beetbox/beets/blob/master/.github/workflows/ci.yaml#L116-L117

I'd definitely want to use flake8 but it might be a simple solution like pip-installing during our gh-actions workflow run and then just executing flake8 from cli for all our files/the whole repo. Would require some trial and error approaches to get ir right so it makes sense and is informational to the contributor.

Furthermore, proper excludes of certain errors and warnings will most probably be required. Not every single pep8/flake8 thing might be suitable for our project.

auto discover own server name

The media delete apis need the own server name to be given as an argument.

I have solved this by instead grabbing it via server_name = requests.get(f"{self.base_url}/_matrix/key/v2/server").json()['server_name'] instead. If you want I can submit a patch for this.

Reduce postgresql database size?

Hello,

My synapse postgres database is consuming quite a bit of space. Is there any way to find out which room is the reason and how can i trim it down?

Thanks

Add useful raw SQL database queries to synadm tool

Some useful data about Synapse statistics isn't available via API calls, but only via raw SQL queries to database.
Will be good to extend synadm tool for integrate some SQL queries into it, here is list of some popular queries: https://github.com/matrix-org/synapse/wiki/SQL-for-analyzing-Synapse-PostgreSQL-database-stats

From that list, especially useful are commands for check last room and user activity:

  • Show top 20 rooms by new events count in last 1 day
  • Show top 20 users on homeserver by sent events (messages) at last month

Room Id not correct

I got the room id using,

$ synadm room list -s state_events|head -3
room_id                                      name                                 canonical_alias                              joined_members    joined_local_members    version  creator                            encryption            federatable    public    join_rules    guest_access    history_visibility      state_events
-------------------------------------------  -----------------------------------  -----------------------------------------  ----------------  ----------------------  ---------  ---------------------------------  --------------------  -------------  --------  ------------  --------------  --------------------  --------------
!kbmCAHJXpbUYAMzNZc:matrix.org                                                                                                              1                       0          1                                                           True           False                                                                 277550

But when i try to get its details, it doesnt work.

$ synadm room details kbmCAHJXpbUYAMzNZc:matrix.org
WARNING Synapse returned status code 404
errcode  M_NOT_FOUND
error    Room not found

How do i get the room id?

Unable to delete room (status code 500)

I am opening a new issue as I think this might be a different problem to the other 'unable to delete room' issue that's currently open.

Using 'synadm room delete 'room-id'' I get the following from synadm:

WARNING Synapse returned status code 500
errcode  M_UNKNOWN
error    Internal server error

I can see the room details before that message, so the room details are accessible.

The HS log shows this:

Traceback (most recent call last):
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/http/server.py", line 366, in _async_render_wrapper
    callback_return = await self._async_render(request)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/http/server.py", line 572, in _async_render
    callback_return = await raw_callback_return
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/rest/admin/rooms.py", line 311, in on_DELETE
    return await self._delete_room(
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/rest/admin/rooms.py", line 368, in _delete_room
    await pagination_handler.purge_room(room_id, force=force_purge)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/handlers/pagination.py", line 417, in purge_room
    await self.storage.purge_events.purge_room(room_id)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/purge_events.py", line 36, in purge_room
    state_groups_to_delete = await self.stores.main.purge_room(room_id)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/databases/main/purge_events.py", line 320, in purge_room
    return await self.db_pool.runInteraction(
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 834, in runInteraction
    return await delay_cancellation(_runInteraction())
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/internet/defer.py", line 1656, in _inlineCallbacks
    result = current_context.run(
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/python/failure.py", line 514, in throwExceptionIntoGenerator
    return g.throw(self.type, self.value, self.tb)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 806, in _runInteraction
    result = await self.runWithConnection(
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 929, in runWithConnection
    return await make_deferred_yieldable(
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/python/threadpool.py", line 244, in inContext
    result = inContext.theWork()  # type: ignore[attr-defined]
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/python/threadpool.py", line 260, in <lambda>
    inContext.theWork = lambda: context.call(  # type: ignore[attr-defined]
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/python/context.py", line 117, in callWithContext
    return self.currentContext().callWithContext(ctx, func, *args, **kw)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/python/context.py", line 82, in callWithContext
    return func(*args, **kw)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/twisted/enterprise/adbapi.py", line 282, in _runWithConnection
    result = func(conn, *args, **kw)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 922, in inner_func
    return func(db_conn, *args, **kwargs)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 670, in new_transaction
    r = func(cursor, *args, **kwargs)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/databases/main/purge_events.py", line 328, in _purge_room_txn
    txn.execute("DELETE FROM rooms WHERE room_id = ?", (room_id,))
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 352, in execute
    self._do_execute(self.txn.execute, sql, *args)
  File "/opt/venvs/matrix-synapse/lib/python3.9/site-packages/synapse/storage/database.py", line 394, in _do_execute
    return func(sql, *args, **kwargs)
psycopg2.errors.ForeignKeyViolation: update or delete on table "rooms" violates foreign key constraint "destination_rooms_room_id_fkey" on table "destination_rooms"
DETAIL:  Key (room_id)=(!zjYxZkVEqwWcQQhXxc:techlore.net) is still referenced from table "destination_rooms".

Add a LICENSE file

This project currently has no license, thus packaging it somewhere isn't possible. Could you add a license file?

media list throws a KeyError in human output mode

Happens when synadm media list -u 'unknown/not valid user' is called.

(synadm) jojo@jum ~/git/govynnus_synadm (regtok) $ synadm -vv -o human media list -u '123'
DEBUG Config entry read. user: admin
DEBUG Config entry read. token: SECRET
DEBUG Config entry read. base_url: http://localhost:8008
DEBUG Config entry read. admin_path: /_synapse/admin
DEBUG Config entry read. matrix_path: /_matrix
DEBUG Config entry read. timeout: 3600
DEBUG Config entry read. format: yaml
DEBUG Formatter in use: human - <function humanize at 0x7f6c5260cea0>
INFO  Querying get on http://localhost:8008/_synapse/admin/v1/users/123/media
WARNING Synapse returned status code 400
Traceback (most recent call last):
  File "/home/jojo/.venvs/synadm/bin/synadm", line 11, in <module>
    load_entry_point('synadm', 'console_scripts', 'synadm')()
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/jojo/git/govynnus_synadm/synadm/cli/media.py", line 83, in media_list_cmd
    from_=from_, limit=limit, sort=sort, reverse=reverse)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/jojo/.venvs/synadm/lib/python3.6/site-packages/click-7.1.2-py3.6.egg/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/home/jojo/git/govynnus_synadm/synadm/cli/user.py", line 394, in user_media_cmd
    .format(media["total"]))
KeyError: 'total'

User modify API requires a redesign

https://github.com/gergelypolonkai/synadm/blob/dba8132dd74fe4be1129a78e07dd26806a6331d8/synadm/api.py#L536-L561

None is not considered truthy, so using None as "default" would cause the values to never change. This makes for a poorly functional API that is also very poorly defined (the severe lack of docs is included).

Basically, we have a problem that has nothing to do with this PR, but the PR has the same problems.
We can merge this PR without fixing the issue, but we definitely need to fix the issue which is more than just user types (trying to remove admin doesn't work because of a similar issue (False will not pass an implicit if condition)).

The user modify API and command works together poorly, unable to remove admin rights (too lazy to write that word). This is because it's using implicit if conditions, where values of False and None is used as defined values, but is interpreted as non-defined values.

Example: you can give admin, not remove. you can set user type (#90), you can't revert to default user type.

Originally posted by @JacksonChen666 in #90 (comment)

synadm stopped functioning on server

Before today, I have successfully ran synadm on this Matrix Synapse server. I'm not sure the last time I checked the functionality on this server as it is a single-user server. It was upgraded to the latest Matrix Synapse, which was released today/yesterday, of version 1.67.0. This may have been working perfectly fine with Matrix Synapse v 1.66.0, but again not sure when I last used it on this server.

System OS: Debian 11 Bullseye

Today, it will not function, and this is the output I receive:

$ synadm user list
WARNING Synapse returned status code 401
Traceback (most recent call last):
  File "/usr/local/bin/synadm", line 33, in <module>
    sys.exit(load_entry_point('synadm==0.36', 'console_scripts', 'synadm')())
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/synadm-0.36-py3.9.egg/synadm/cli/user.py", line 80, in list_user_cmd
KeyError: 'total'

I then followed the upgrade instructions in the README.md, but afterwards I receive the same error message.

Abbreviations of output formats

Abbreviating an output format should be possible

$ synadm -o pp user search test
Usage: synadm [OPTIONS] COMMAND [ARGS]...
Try "synadm -h" for help.

Error: Invalid value for "--output" / "-o": invalid choice: pp. (choose from yaml, json, human, pprint)

Add ability to call custom GET/POST command via synadm script

Will be good to use same synadm tool not only for pre-defined API calls, but for custom too, for not to create custom scripts for each call.
For example, allow something like this:

synadm custom-api-call --type POST --path "/_synapse/admin/v1/media/ru-matrix.org/delete?before_ts=1602745639000" --body "{}"

where make default argument value for type as GET, body = {}.

This way will be much more comfortable, than composing full curl command in cli manually and remember your token each time.

Especially this is useful at now, when some of popular API calls are not implemented in build-in commands.

Catch non-existent rooms on room delete earlier

In interactive room delete synadm room delete <id> (without --batch flag): Even if a room is not found in the first place, it's still asked wheter it should be deleted. This could be catched earlier. If a synadm user would like to "try it anyway" the bach flag still can be used, since it omits the "room info queries".

~ $ synadm -v room delete '!aBXqGDWIxVYeYxVbRu:matrix.org'
INFO  Querying get on https://mysynapse.org/_synapse/admin/v1/rooms/!ABCqGDWIxVYeYxVxyz:example.org
WARNING Synapse returned status code 404
errcode: M_NOT_FOUND
error: Room not found

INFO  Querying get on https://mysynapse.org/_synapse/admin/v1/rooms/!ABCqGDWIxVYeYxVxyz:example.org/members
WARNING Synapse returned status code 404
errcode: M_NOT_FOUND
error: Room not found

Are you sure you want to delete this room? (y/N): y
INFO  Querying delete on https://mysynapse.org/_synapse/admin/v1/rooms/!ABCqGDWIxVYeYxVxyz:example.org
failed_to_kick_users: []
kicked_users: []
local_aliases: []
new_room_id: null

~ $ 

missing/undocumented dependency: setuptools

Following the installation process on my server (Debian 10) I get the following error:

# python3 setup.py install
Traceback (most recent call last):
  File "setup.py", line 18, in <module>
    from setuptools import setup, find_packages
ModuleNotFoundError: No module named 'setuptools'

The solution for me was apt install python3-setuptools.

Special internal room IDs breaks some commands

A user could not delete a room because the internal room ID was very special:

!test/v7 ๐Ÿˆ๏ธ:maunium.net (alias thingy is #test/v7:maunium.net)

The / and the emoji should be URL encoded (or just everything special) so that synapse doesn't misinterpret the / and the emoji doesn't cause weird issues.
That way, even if the room ID contains special characters, it should "just work" without having to resort to a workaround (manually URL encoding the room ID, so it's %21test%2Fv7%20%F0%9F%90%88%EF%B8%8F%3Amaunium.net instead).

Solution for user ref, Handy URL encode code snippet
Given the code needed to URL encode in python, I would put this label an "easy issue" but I'm not sure what changes are needed to make sure room IDs are URL encoded (there probably is also multiple places to also handle it sooooo).
Related issues in another project: Awesome-Technologies/synapse-admin#111

synadm doesn't work after v0.38 with Click 7.0

Hi, there.

Thank you for creating and maintaining synadm. I have been using it for a while on Ubuntu 20.04 and after I updated today to version 0.41 it stopped working with this error:

Traceback (most recent call last):
  File "/home/ubuntu/.local/bin/synadm", line 5, in <module>
    from synadm.cli import root
  File "/home/ubuntu/.local/lib/python3.8/site-packages/synadm/cli/__init__.py", line 486, in <module>
    from synadm.cli import room, user, media, group, history, matrix, regtok, notice  # noqa: F401, E402, E501
  File "/home/ubuntu/.local/lib/python3.8/site-packages/synadm/cli/room.py", line 336, in <module>
    def block(helper, room_id, block):
  File "/usr/lib/python3/dist-packages/click/decorators.py", line 173, in decorator
    _param_memo(f, OptionClass(param_decls, **option_attrs))
  File "/usr/lib/python3/dist-packages/click/core.py", line 1601, in __init__
    raise TypeError('Got secondary option for non boolean flag.')
TypeError: Got secondary option for non boolean flag.

Then I noticed this when upgrading:

Requirement already satisfied: Click<8.0,>=7.0 in /usr/lib/python3/dist-packages (from synadm) (7.0)

And then tried to install only this package (newer version from pip), what worked:

pรญp install -U click

Now I have this for my user:

$ pip list --user      
Package            Version
------------------ -------
click              8.1.3  
click-option-group 0.5.5  
dnspython          2.3.0  
synadm             0.41 

I did not find click in logs, so I guess that was the version installed by default on Ubuntu 20.04. Maybe the version needs to be updated in the dependencies.

PS: while troubleshooting it seems that the builtin version works with synadm up to v0.38 and fails after that.

Regards

How to get more detailed help of some command?

There is exist a general help command, that shows the help text:

synadm -h

In script code I see more detailed help text for commands, for example for room delete command:

@click.option('--new-room-user-id', '-u', type=str,

But I can't understand how to show it via command line arguments?
I try those:

synadm room delete
synadm -h room
synadm -h room delete

But no one shows me the detailed help text with list of available arguments.

User modify command doesn't show difference in non-interactive output mode

When using the non-interactive/batch flag, the output of "what will be changed" and the final "spitting out" of "what has been changed" are stuck together, thus it's not obvious what has happened:

$ synadm --non-interactive user modify --user-type support testuser6 
Current user account settings:
admin: 0
appservice_id: null
avatar_url: null
consent_server_notice_sent: null
consent_version: null
creation_ts: 1605467620
deactivated: 1
displayname: testuser6
external_ids: []
is_guest: 0
name: '@testuser6:example.org'
password_hash: null
shadow_banned: false
threepids: []
user_type: null

User account settings to be modified:
user_type: support
admin: 0
appservice_id: null
avatar_url: null
consent_server_notice_sent: null
consent_version: null
creation_ts: 1605467620
deactivated: 1
displayname: testuser6
external_ids: []
is_guest: 0
name: '@testuser6:example.org'
password_hash: null
shadow_banned: false
threepids: []
user_type: support

Force user logout option

There should be an option to invalidate all access tokens for particular user = force logout.

Unable to delete room

I'm trying to use synadm to delete a room from my server, but I keep getting that the following:

$ synadm room delete OMITTED-ROOM_ID
WARNING Synapse returned status code 404
errcode  M_NOT_FOUND
error    Room not found
WARNING Synapse returned status code 404
Traceback (most recent call last):
  File "/usr/local/bin/synadm", line 33, in <module>
    sys.exit(load_entry_point('synadm==0.34', 'console_scripts', 'synadm')())
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/dietpi/.local/lib/python3.9/site-packages/synadm/cli/room.py", line 249, in delete
    ctx.invoke(members, room_id=room_id)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/home/dietpi/.local/lib/python3.9/site-packages/synadm/cli/room.py", line 206, in members
    .format(room_members["total"]))
KeyError: 'total'

I then tried updating my synadm, but was still unable to delete the room.

I then tried creating a completely new room and synadm won't delete it either.

It doesn't matter what I use for ROOM_ID or if I use ROOM_ID:server-name or anything - it all fails.

Neither synadm room delete -h nor the Github README give me any useful information about what ROOM_ID needs to be syntax-wise. Is there special syntax for the ROOM_ID?

I figured my test case with deleting a newly created room would give me the info I need, but it's all failing.

Add a way to see synadm version

I was running synadm v 0.38 (as seen in my local setup.py) and followed the update git installation section (which has been successful in the past for me).

I get the following errors when running pip install .

$ pip install .
Defaulting to user installation because normal site-packages is not writeable
Processing /home/dietpi/synadm
  Preparing metadata (setup.py) ... done
Requirement already satisfied: Click<9.0,>=7.1 in /usr/local/lib/python3.9/dist-packages/click-7.1.2-py3.9.egg (from synadm==0.42) (7.1.2)
Requirement already satisfied: requests in /usr/local/lib/python3.9/dist-packages (from synadm==0.42) (2.26.0)
Requirement already satisfied: tabulate in /usr/local/lib/python3.9/dist-packages/tabulate-0.8.9-py3.9.egg (from synadm==0.42) (0.8.9)
Requirement already satisfied: PyYaml in /usr/local/lib/python3.9/dist-packages (from synadm==0.42) (5.4.1)
Requirement already satisfied: click-option-group>=0.5.2 in /usr/local/lib/python3.9/dist-packages/click_option_group-0.5.3-py3.9.egg (from synadm==0.42) (0.5.3)
Requirement already satisfied: dnspython in /usr/local/lib/python3.9/dist-packages/dnspython-2.3.0-py3.9.egg (from synadm==0.42) (2.3.0)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.9/dist-packages (from requests->synadm==0.42) (1.26.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/dist-packages (from requests->synadm==0.42) (2021.5.30)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.9/dist-packages (from requests->synadm==0.42) (3.2)
Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.9/dist-packages (from requests->synadm==0.42) (2.0.6)
Building wheels for collected packages: synadm
  Building wheel for synadm (setup.py) ... error
  error: subprocess-exited-with-error
  
  ร— python setup.py bdist_wheel did not run successfully.
  โ”‚ exit code: 1
  โ•ฐโ”€> [5 lines of output]
      running bdist_wheel
      running build
      running build_py
      copying synadm/api.py -> build/lib/synadm
      error: could not delete 'build/lib/synadm/api.py': Permission denied
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for synadm
  Running setup.py clean for synadm
Failed to build synadm
Installing collected packages: synadm
  Attempting uninstall: synadm
    Found existing installation: synadm 0.33.1
    Uninstalling synadm-0.33.1:
      Successfully uninstalled synadm-0.33.1
  Running setup.py install for synadm ... error
  error: subprocess-exited-with-error
  
  ร— Running setup.py install for synadm did not run successfully.
  โ”‚ exit code: 1
  โ•ฐโ”€> [7 lines of output]
      running install
      /home/dietpi/.local/lib/python3.9/site-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
        warnings.warn(
      running build
      running build_py
      copying synadm/api.py -> build/lib/synadm
      error: could not delete 'build/lib/synadm/api.py': Permission denied
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
  Rolling back uninstall of synadm
  Moving to /home/dietpi/.local/bin/synadm
   from /tmp/pip-uninstall-onmzdjc2/synadm
  Moving to /home/dietpi/.local/lib/python3.9/site-packages/synadm-0.33.1.dist-info/
   from /home/dietpi/.local/lib/python3.9/site-packages/~ynadm-0.33.1.dist-info
  Moving to /home/dietpi/.local/lib/python3.9/site-packages/synadm/
   from /home/dietpi/.local/lib/python3.9/site-packages/~ynadm
error: legacy-install-failure

ร— Encountered error while trying to install package.
โ•ฐโ”€> synadm

note: This is an issue with the package mentioned above, not pip.
hint: See above for output from the failure.
--- Logging error ---
Traceback (most recent call last):
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/utils/logging.py", line 177, in emit
    self.console.print(renderable, overflow="ignore", crop=False, style=style)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_vendor/rich/console.py", line 1673, in print
    extend(render(renderable, render_options))
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_vendor/rich/console.py", line 1305, in render
    for render_output in iter_render:
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/utils/logging.py", line 134, in __rich_console__
    for line in lines:
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_vendor/rich/segment.py", line 249, in split_lines
    for segment in segments:
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_vendor/rich/console.py", line 1283, in render
    renderable = rich_cast(renderable)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_vendor/rich/protocol.py", line 36, in rich_cast
    renderable = cast_method()
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/self_outdated_check.py", line 130, in __rich__
    pip_cmd = get_best_invocation_for_this_pip()
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/utils/entrypoints.py", line 58, in get_best_invocation_for_this_pip
    if found_executable and os.path.samefile(
  File "/usr/lib/python3.9/genericpath.py", line 101, in samefile
    s2 = os.stat(f2)
FileNotFoundError: [Errno 2] No such file or directory: '/usr/bin/pip'
Call stack:
  File "/usr/local/bin/pip", line 8, in <module>
    sys.exit(main())
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/cli/main.py", line 70, in main
    return command.main(cmd_args)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/cli/base_command.py", line 101, in main
    return self._main(args)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/cli/base_command.py", line 223, in _main
    self.handle_pip_version_check(options)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/cli/req_command.py", line 190, in handle_pip_version_check
    pip_self_version_check(session, options)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/self_outdated_check.py", line 236, in pip_self_version_check
    logger.warning("[present-rich] %s", upgrade_prompt)
  File "/usr/lib/python3.9/logging/__init__.py", line 1454, in warning
    self._log(WARNING, msg, args, **kwargs)
  File "/usr/lib/python3.9/logging/__init__.py", line 1585, in _log
    self.handle(record)
  File "/usr/lib/python3.9/logging/__init__.py", line 1595, in handle
    self.callHandlers(record)
  File "/usr/lib/python3.9/logging/__init__.py", line 1657, in callHandlers
    hdlr.handle(record)
  File "/usr/lib/python3.9/logging/__init__.py", line 948, in handle
    self.emit(record)
  File "/home/dietpi/.local/lib/python3.9/site-packages/pip/_internal/utils/logging.py", line 179, in emit
    self.handleError(record)
Message: '[present-rich] %s'
Arguments: (UpgradePrompt(old='22.2.2', new='23.2.1'),)

After looking at these errors, it makes me think I'm running v 0.33.1 but I thought I was running v 0.38 and I want to be running the latest v 0.42. When I run synadm -h it's not clear what version I'm actually running/using. Is there another way to check this or fix this?

Additional info
I know python & pip are functioning as I've installed Matrix Synapse using this method and have been upgrading it this way for 3 years - I get the same --- Logging error --- section when doing this, but that's a different unrelated issue.

catch error on incomplete config after "auto-reconfigure"

Catch error when user execs a regular command eg. synadm user list - config is found to be incomplete - configurator is launched autoatically - user again does not enter all requirded information.

Either error should be catched or configuator should force user to input all required information and don't accept input empty strings (eg by just hitting enter)

ERROR config entry user missing
Traceback (most recent call last):
  File "/Users/jojo/.venvs/synadm_after_pr/bin/synadm", line 11, in <module>
    load_entry_point('synadm==0.14', 'console_scripts', 'synadm')()
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/click-7.1.2-py3.7.egg/click/decorators.py", line 33, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/Users/jojo/.venvs/synadm_after_pr/lib/python3.7/site-packages/synadm-0.14-py3.7.egg/synadm/cli/user.py", line 47, in list_user_cmd
AttributeError: 'NoneType' object has no attribute 'user_list'

Deactivating multiple users at once

Hello,

Out of convenience, I am missing a feature for deactivating multiple users at once, preferbly based on a given regular expression or any other kind of matching pattern.

For this to work, all users must be queried and for each it must be check if the MXIDs localpart matches the regular expression. I assume that the list command can not be used for this at its current implementation. The first problem is, that the API limits how many users are returned and as far as I know there is no way of telling the API ro return all users without limit in one request. Second, the current way of filtering for users during the list command is not exactly the same as a pattern matching, since the fitler option probably only checks wheter a user ID contains a given substring or not, which is not exactly what I am looking for.

After the query is finished, the deactivate command has to take the list of user IDs returned by the modified list command and deactivate each of the accounts.

Off course this feature is entirely optional and is not coverd directly by the API at all.

Config file/directory is created world-readable

The ~/.config/synadm.yaml file (and the ~/.config/directory, if it doesn't already exist) is created with default permissions, so with a typical umask ends up world-readable. But it contains the admin user's access token, so should be explicitly created with mode 0o600 (or 0o700 for the directory).

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.