Code Monkey home page Code Monkey logo

python-qbittorrent's Introduction

python-qBittorrent

Documentation Status image

Python wrapper for qBittorrent Web API (for versions above 4.1, for version below and above v3.1.x please use 0.3.1 version).

For qBittorrent clients with earlier versions, use mookfist's python-qbittorrent.

This wrapper is based on the methods described in qBittorrent's Official Web API Documentation

Some methods are only supported in qBittorent's latest version (v3.3.1 when writing).

It'll be best if you upgrade your client to a latest version.

Installation

The best way is to install a stable release from PyPI:

$ pip install python-qbittorrent

You can also stay on the bleeding edge of the package:

$ git clone https://github.com/v1k45/python-qBittorrent.git
$ cd python-qBittorrent
$ python setup.py install

Prerequisite

qBittorent webUI must be enabled before using this API client. How to enable the qBittorrent Web UI

Quick usage guide

from qbittorrent import Client

qb = Client('http://127.0.0.1:8080/')

qb.login('admin', 'your-secret-password')
# not required when 'Bypass from localhost' setting is active.
# defaults to admin:admin.
# to use defaults, just do qb.login()

torrents = qb.torrents()

for torrent in torrents:
    print torrent['name']

If you have enabled SSL and are using a self-signed certificate, you'd probably want to disable SSL verification. This can be done by passing [verify=False]{.title-ref} while initializing the [Client]{.title-ref}.

from qbittorrent import Client

qb = Client('https://127.0.0.1:8080/', verify=False)

API methods

Getting torrents

  • Get all active torrents:

    qb.torrents()
    
  • Filter torrents:

    qb.torrents(filter='downloading', category='my category')
    # This will return all torrents which are currently
    # downloading and are labeled as ``my category``.
    
    qb.torrents(filter='paused', sort='ratio')
    # This will return all paused torrents sorted by their Leech:Seed ratio.
    

Refer qBittorents WEB API documentation for all possible filters.

Downloading torrents

  • Download torrents by link:

    magnet_link = "magnet:?xt=urn:btih:e334ab9ddd91c10938a7....."
    qb.download_from_link(magnet_link)
    
    # No matter the link is correct or not,
    # method will always return empty JSON object.
    
  • Download multipe torrents by list of links:

    link_list = [link1, link2, link3]
    qb.download_from_link(link_list)
    
  • Downloading torrents by file:

    torrent_file = open('my-torrent-file.torrent', 'rb')
    qb.download_from_file(torrent_file)
    
  • Downloading multiple torrents by using files:

    torrent_file_list = [open('1.torrent', 'rb'), open('2.torrent', 'rb')]
    qb.download_from_file(torrent_file_list)
    
  • Specifing save path for downloads:

    dl_path = '/home/user/Downloads/special-dir/'
    qb.download_from_file(myfile, savepath=dl_path)
    
    # same for links.
    qb.download_from_link(my_magnet_uri, savepath=dl_path)
    
  • Applying labels to downloads:

    qb.download_from_file(myfile, label='secret-files ;) ')
    
    # same for links.
    qb.download_from_link(my_magnet_uri, category='anime')
    

Pause / Resume torrents

  • Pausing/ Resuming all torrents:

    qb.pause_all()
    qb.resume_all()
    
  • Pausing/ Resuming a speicific torrent:

    info_hash = 'e334ab9ddd....infohash....5d7fff526cb4'
    qb.pause(info_hash)
    qb.resume(info_hash)
    
  • Pausing/ Resuming multiple torrents:

    info_hash_list = ['e334ab9ddd9......infohash......fff526cb4',
                      'c9dc36f46d9......infohash......90ebebc46',
                      '4c859243615......infohash......8b1f20108']
    
    qb.pause_multiple(info_hash_list)
    qb.resume_multipe(info_hash_list)
    

Full API method documentation

All API methods of qBittorrent are mentioned @ Read the docs

๐Ÿค Maintainer

๐Ÿค Contributors

Made with contrib.rocks.

TODO

  • Write tests

python-qbittorrent's People

Contributors

bonny1992 avatar c3-christopheha avatar deadsec-security avatar dennka avatar esanchezm avatar psykzz avatar sebclem avatar seeker avatar shaance avatar v1k45 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

python-qbittorrent's Issues

logout method should use POST instead of GET

The method logout need to be updated to use POST instead of GET.

Error:

  File "/usr/local/lib/python3.9/site-packages/qbittorrent/client.py", line 134, in logout
    response = self._get('auth/logout')
  File "/usr/local/lib/python3.9/site-packages/qbittorrent/client.py", line 57, in _get
    return self._request(endpoint, 'get', **kwargs)
  File "/usr/local/lib/python3.9/site-packages/qbittorrent/client.py", line 94, in _request
    request.raise_for_status()
  File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 405 Client Error: Method Not Allowed for url: http://<ip>:8080/api/v2/auth/logout

Per qBittorrent API Docs, looks like since version 4.4.4 http status 405 is throw when wrong method is used. Seems like behavior of this call has changed since original implementation.

Events handling

It'd be very handy to have an event system which would automatically call functions on various events like on_download_started, on_download_progress, on_download_complete, on_download_error, if possible

Get download percentage

Hey there,

I wrote a little code that would automatically download a bunch of torrents. I'm using python-qbittorrent. I was wondering is there a way I can get the download percentage as an output when a new download is started? I read the docs but I fail to find anything that would return this value.

Any help would be appreciated.

LoginRequired

image
image

It's really strange that it always prompts me to login first.

ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8080)

When trying to make a local connection get this error

qb = Client('http://127.0.0.1:8080/')

ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8080): Max retries exceeded with url: /query/preferences (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x77148fc128>: Failed to establish a new connection: [Errno 111] Connection refused'))

Pre-requisite

Do you mind adding a prerequisite section in your documentation mentioning webUI needs to be activated (how to: https://github.com/lgallard/qBittorrent-Controller/wiki/How-to-enable-the-qBittorrent-Web-UI)
?

I was having this issue and struggle for 10 min before understanding that it was not activated by default

equests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8080): Max retries exceeded with url: /query/preferences (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fd4b0225a20>: Failed to establish a new connection: [Errno 111] Connection refused',))

Issue with "rb+" mode

with open("torrent.torrent", "rb") as f:
    qb.download_from_file(f)

works, but

with open("torrent.torrent", "rb+") as f:
    qb.download_from_file(f)

raises

requests.exceptions.HTTPError: 415 Client Error: Unsupported Media Type for url

recheck isnt triggering

I tried to use qb.recheck(torrent['hash']) to make qBittorrent 4.1.3 do a recheck, but it didn't start checking.

To make sure I had the correct hash for the torrent, I tried to pause and resume, then recheck, all with the same hash, and I saw the torrent pause, resume, and then nothing else.

Consider Properties

I see a lot of get_global_download_limit and set_global_download_limit.

Have you considered just making those into properties instead?

api = Client()
api.login('a', 'b')
api.global_download_limit
>>> 5 # Or whatever.

force_start default value is incorrect

The default for the value parameter is not correct. Currently it is set to the Python Boolean True, but it should just be set to the string "true" since that is the text the setForceStart endpoint is expecting. I had to make the change in my local copy of client.py to make force_start work.

    def force_start(self, infohash_list, value=True):

should be

    def force_start(self, infohash_list, value="true"):

url encoding

Hi

I modified the client to add a new method for renaming torrents.

def set_name(self, infohash, name): headers = {'content-type': 'application/x-www-form-urlencoded'} return self._post('api/v2/torrents/rename', data={'hash': infohash.lower(),'name': name}, headers=headers)
However, I seem unable to avoid having all spaces ( ) be turned into plus signs (+).

The qBittorrent Web API documentation includes the example

hash=8c212779b4abde7c6bc608063a0d008b7e40ce32&name=This%20is%20a%20test

But using %20 does not decode to space ( ), but it includes the text '%20' in the name of the torrent.

This is my first time modifying an already existing library, so I do not know whether I'm explaining this correctly. Please forgive if not.

Python 3 Support?

Is Python 3 support on the roadmap? =)

From what I can gather, it doesn't currently support Python 3 - e.g.:

In [8]: torrents = qb.torrents()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-8-f45e4320e6c2> in <module>()
----> 1 torrents = qb.torrents()

/usr/local/lib/python3.6/site-packages/qbittorrent/client.py in torrents(self, **filters)
    162         """
    163         params = {}
--> 164         for name, value in filters.iteritems():
    165             # make sure that old 'status' argument still works
    166             name = 'filter' if name == 'status' else name

AttributeError: 'dict' object has no attribute 'iteritems'

toggle_alternative_speed use GET insted of POST

The method toggle_alternative_speed make a GET request on transfer/toggleSpeedLimitsMode where it should be a POST.

I got this error:

405 Client Error: Method Not Allowed for url: https://xxxxx/api/v2/transfer/toggleSpeedLimitsMode

I have checked on the network tab on the dev console while on the web UI, this should be a POST request:
image

error(104, 'Connection reset by peer')

i'm running qbittorrent in a box along with couchpotato (a movies pvr). the box is running xubuntu 16.04. i have never worked with python but apparently couchpotato uses your module to talk to qbittorrent's api. everytime it tries to retrieve the list of torrents and their status, it gets error(104, 'Connection reset by peer'). it doesn't happen when there are just a few torrents in the client, but right now i have +500 of them and it has been over +1000. any input?

This is potato's error log: [.downloaders.qbittorrent_] Failed to get status from qBittorrent: ('Connection aborted.', error(104, 'Connection reset by peer'))

Runtime Error

I had my staff working with qbittorrent and python-qbittorrent, it only scrapped some website and donwload torrents of my interest. But now I'm getting this error:

his wrapper only supports qBittorrent applications
with version higher than 3.1.x.
Please use the latest qBittorrent release.

I have installed the 3.3.6 version of qbittorrent, the 2.7.11 version of python and the 0.1.8 version of python-qbittorrent. I have done serveral upgrades and downgrades but nothing seems to work for me, any idea on how can I solve this problem?

Thanks a lot

qb.download_from_link doesn't work with Lists

Qbittorrent: 4.0.4
OS: Windows 10
Python: 2.7

Downloadlist is a list populated with a few items.
Category is "Movies" in this case.
I always get into the Except-State.

`if len(Downloadlist)>0:

try:

    qb.download_from_link(Downloadlist, category=category)

    for i in Downloadlist:

        completed.append(i)

        print("Added: ", i)

except:

    print("Download could not be added to qBittorrent\n")

`

download with SequentialDownload

improvement proposal: there should be a way to toggle sequential download when adding torrent initially from download_from_link , like download_from_link(file, savepath, sequential=True)., like with qbit GUI client. The reason is that there is a delay when adding a torrent to download, so when download_from_link is called followed immediately with toggle_sequential_download, the torrent may not have been initialized yet, so the toggle is simply ignored.

I can't import the package normally.

python-qbittorrent 0.31
python 3.7.1

Traceback (most recent call last): File "G:/1python/qbittorrent.py", line 1, in <module> from qbittorrent import Client File "G:\1python\qbittorrent.py", line 1, in <module> from qbittorrent import Client ImportError: cannot import name 'Client' from 'qbittorrent' (G:\1python\qbittorrent.py)

image

Thank you for helping me.

Manual installation

Hi,
I can't install it directly from qBittorent doesn't start, how can I install it manually?
Thanks

Login fails under qBittorrent 3.1.11

Following the example labeled "Quick Usage Guide" results in an exception.
python-qbittorrent version: 0.1.4
qBittorrent version 3.1.11
requests version 2.8.1

Traceback:
Traceback (most recent call last):
File "./temp.py", line 13, in
torrents = qb.torrents()
File "/usr/local/lib/python2.7/dist-packages/qbittorrent/client.py", line 168, in torrents
return self._get('query/torrents', params=params)
File "/usr/local/lib/python2.7/dist-packages/qbittorrent/client.py", line 34, in _get
return self._request(endpoint, 'get', **kwargs)
File "/usr/local/lib/python2.7/dist-packages/qbittorrent/client.py", line 62, in _request
raise LoginRequired
qbittorrent.client.LoginRequired: Please login first.

The Web UI is enabled in preferences, accessible with a browser and the username/password combination works in the browser. I have also tested with different passwords, and bypassing authentication for localhost. All these have the same result.

Looking at the login code, and replicating it with requests directly,

session = requests.Session()
login = session.post('http://127.0.0.1:8080/login', data={'username': 'admin', 'password': 'admin'})
print login.status_code

results in a status code of '404'. If there's more info that would be helpful, let me know.

API calls fails unless referrer is provided

Hi!
I am trying to use this module, but it doesn't work with qBittorrent 3.3.13 on Archlinux x64. All API calls fails unless I specify referrer like this:

        self.session = requests.Session()
        header = {
            'Referer': 'http://localhost:8080'
        }
        login = self.session.post(self.url+'login',
                                  data={'username': username,
                                        'password': password},
                                  headers=header)
        if login.text == 'Ok.':
            self._is_authenticated = True
        else:
            return login.text

I have no experience with qBittorrent, is it my misconfiguration or is it some API update and should be fixed in this library?

python: 3.6.0
requests: 2.18.1

Change URL Login

On qbittorrent.Client.__init__ line 31 change

From: prefs_url = self.url + 'app/preferences'
To: prefs_url = self.url + 'auth/login'

This change is nescessary to login in new versions of the qbittorrent webUI

Adjust to new v4.1+ API

The latest version of the package doesn't seem to be compatible with the new 4.2.0 version of qBittorrent

Incompatible with 'Bypass authentication for localhost' option in qBittorrent

In the WebUI options for qBittorrent, there is an option to bypass authentication if the requests come from localhost. If this option is enabled, any requests from localhost do not have to authenticate before making requests; e.g. I should be able to GET /query/torrents without first POSTing to /login.
Right now, without calling Client#login, calling Client#torrents will raise a LoginRequired error when I shouldn't need any authentication as the request is coming from my local machine.

In set_file_priority, how to get the file_id?

Or I am doing something wrong, or the library isn't working?

    qb = Client('http://127.0.0.1:8080/')
    qb.login('name', 'pass')
    for torrent in qb.torrents():
        arqs = qb.get_torrent_files(torrent['hash'])
        for arq in arqs:
            qb.set_file_priority(torrent['hash'], file_id=arq['index'], priority=1) #or 0 or any value

I don't see the changes with this code. How do I get the file_id? Is the index data? There is no other obvious value.
What am I doing wrong? Some values change, but it is a little random. So I think the index isn't the correct number.

Documentation error:

The first example of
Get all active torrents:
is
qb.torrents()

This is incorrect. This returns only 10 torrents.

def torrents( self, status='active', label='', sort='priority',                     
                 reverse=False, limit=10, offset=0):   

Actually, this is a coding error. The default limit should be 85,000 or 65,336 or 99,999 or some clearly 'maximum ever' number. Currently, the limit must be inserted in every function call. Instead, it should only be required when it is ... required: when a limit is needed.

Expiring authentication is not handled

The authentication cookie expires after a while, and after that API calls fail. python-qBittorrent should handle the reauthentication.

To reproduce the problem:

  1. make any API calls, and notice that it works
  2. sleep for an hour
  3. make the same API call, and notice that it fails with 403 Client Error: Forbidden for url

Unsupported Media Type error when attempting to download_from_file

We got past the last error, but now I'm getting a new one

tor = r"C:\Torrents\add_torrent\sample2.torrent"
f = open(tor)
o.qb.download_from_file(f, label='comics')

This gives the following error

Traceback (most recent call last):
  File "C:\Torrents\utilities\qbittorrent_port_updater.py", line 115, in <module>
    start()
  File "C:\Torrents\utilities\qbittorrent_port_updater.py", line 43, in start
    nt.check(o)
  File "C:\Torrents\utilities\qbittorrent_port_updater.py", line 98, in check
    o.qb.download_from_file(f, label='comics')
  File "C:\Python27\lib\site-packages\qbittorrent\client.py", line 325, in download_from_file
    return self._post('command/upload', data=data, files=torrent_files)
  File "C:\Python27\lib\site-packages\qbittorrent\client.py", line 46, in _post
    return self._request(endpoint, 'post', data, **kwargs)
  File "C:\Python27\lib\site-packages\qbittorrent\client.py", line 70, in _request
    request.raise_for_status()
  File "C:\Python27\lib\site-packages\requests\models.py", line 840, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 415 Client Error: Unsupported Media Type for url: http://torrent:8080/command/upload

how to wait untill download is finished

plz help me with this wait untill download finished and do something
torrent_file = open('game.torrent', 'rb')
qb.download_from_file(torrent_file, save_path=os.getcwd())
d = qb.torrents()
for torrent in d:
name = torrent['name']
hash = torrent['hash']

now how to print download progress
or wait untill download is finished

There is no way to disable ssl certificate check

I'm using https + Let's encrypt. But sometimes need to test something with self-signed certs. I got

>>>qb = Client('htts://<my-domain>:8080/') Traceback (most recent call last): File "/home/tg/.local/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 485, in wrap_socket cnx.do_handshake() File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1808, in do_handshake self._raise_ssl_error(self._ssl, result) File "/usr/lib/python3/dist-packages/OpenSSL/SSL.py", line 1548, in _raise_ssl_error _raise_current_error() File "/usr/lib/python3/dist-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue raise exception_type(errors) OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]

Currently need to patch client.py (add verify=False in requests call). IMHO will be nice to have an option, where user can disable ssl verification.

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 18301: invalid start byte

i get this error when i try to load a torrent from file

qbittorrent = Client('http://myip:8080/')
qbittorrent.login('admin', 'my password')
with open(torrent_file, "r") as file:
    qbittorrent.download_from_file(file_buffer=file, save_path=location)
Traceback (most recent call last):
  File "deluxe/make_torrent.py", line 66, in <module>
    qbittorrent.download_from_file(file_buffer=file, save_path=location)
  File "/usr/lib/python3.8/site-packages/qbittorrent/client.py", line 384, in download_from_file
    return self._post('torrents/add', data=data, files=torrent_files)
  File "/usr/lib/python3.8/site-packages/qbittorrent/client.py", line 64, in _post
    return self._request(endpoint, 'post', data, **kwargs)
  File "/usr/lib/python3.8/site-packages/qbittorrent/client.py", line 86, in _request
    request = self.session.post(final_url, data, **kwargs)
  File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 581, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 519, in request
    prep = self.prepare_request(req)
  File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 452, in prepare_request
    p.prepare(
  File "/usr/lib/python3.8/site-packages/requests/models.py", line 316, in prepare
    self.prepare_body(data, files, json)
  File "/usr/lib/python3.8/site-packages/requests/models.py", line 504, in prepare_body
    (body, content_type) = self._encode_files(files, data)
  File "/usr/lib/python3.8/site-packages/requests/models.py", line 159, in _encode_files
    fdata = fp.read()
  File "/usr/lib/python3.8/codecs.py", line 322, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 18301: invalid start byte

ValueError when attempting download_from_file

Can you please help understand what I am doing wrong here? I am just trying to add a torrent from a torrent file to qbittorrent v3.3.3.

tor = r"\\server\d\Downloads\AddTorrentComic\sample1.torrent"
f = open(tor)
o.qb.download_from_file(f, label='comics')

This gives me the following error:

Traceback (most recent call last):
  File "C:\Torrents\utilities\qbittorrent_port_updater.py", line 111, in <module>
    start()
  File "C:\Torrents\utilities\qbittorrent_port_updater.py", line 39, in start
    nt.check(o)
  File "C:\Torrents\utilities\qbittorrent_port_updater.py", line 94, in check
    o.qb.download_from_file(f, label='comics')
  File "C:\Python27\lib\site-packages\qbittorrent\client.py", line 321, in download_from_file
    return self._post('command/upload', data=data, files=torrent_files)
  File "C:\Python27\lib\site-packages\qbittorrent\client.py", line 46, in _post
    return self._request(endpoint, 'post', data, **kwargs)
  File "C:\Python27\lib\site-packages\qbittorrent\client.py", line 68, in _request
    request = rq.post(final_url, data, **kwargs)
  File "C:\Python27\lib\site-packages\requests\sessions.py", line 511, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "C:\Python27\lib\site-packages\requests\sessions.py", line 454, in request
    prep = self.prepare_request(req)
  File "C:\Python27\lib\site-packages\requests\sessions.py", line 388, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "C:\Python27\lib\site-packages\requests\models.py", line 296, in prepare
    self.prepare_body(data, files, json)
  File "C:\Python27\lib\site-packages\requests\models.py", line 447, in prepare_body
    (body, content_type) = self._encode_files(files, data)
  File "C:\Python27\lib\site-packages\requests\models.py", line 132, in _encode_files
    for (k, v) in files:
ValueError: need more than 1 value to unpack

savepath argument in download_from_link method replaces spaces with plus sign

This issue was reported by Wueno Gracia through email.

The if the savepath argument in dowload_from_link method contains spaces, they are converted to plus sign (+) resulting in wrong directory name.

For example:

qb.download_from_link('magnet?xt.....', '/home/username/Downloads/custom directory')

downloads the torrent in home/username/Downloads/custom+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.