├── steemconnect
├── __init__.py
├── defaults.py
├── mixins.py
├── utils.py
├── client.py
└── operations.py
├── setup.py
├── README.md
├── examples
└── login_app
│ └── login_app.py
├── docs
├── hot-signing.rst
├── example-flask-app.rst
├── index.rst
├── gettingstarted.rst
├── usingtheaccesstoken.rst
├── Makefile
└── conf.py
├── LICENSE
├── .gitignore
└── tests.py
/steemconnect/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/steemconnect/defaults.py:
--------------------------------------------------------------------------------
1 | OAUTH_BASE_URL = "https://steemconnect.com/oauth2/"
2 | SC2_API_BASE_URL = "https://steemconnect.com/api/"
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='steemconnect',
5 | version='0.0.6',
6 | packages=['steemconnect'],
7 | url='https://github.com/emre/steemconnect',
8 | license='MIT',
9 | author='Emre Yilmaz',
10 | author_email='mail@emreyilmaz.me',
11 | description='Python client library of SteemConnect',
12 | install_requires=["requests", "responses"]
13 | )
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | steemconnect-python-client is a simple yet powerful library to interact with the [Steemconnect](https://steemconnect.com). There was no *production ready* library for Python (Or I couldn't find it.) so I have decided to write my own.
2 |
3 | #### Installation
4 |
5 | ```
6 | $ (sudo) pip install steemconnect
7 | ```
8 |
9 | #### Documentation
10 |
11 | https://steemconnect-python-client.readthedocs.io/en/latest/
12 |
--------------------------------------------------------------------------------
/steemconnect/mixins.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 |
4 | class CustomJsonMixin:
5 |
6 | def get_base_op_structure(self, required_auths, required_posting_auths,
7 | custom_operation_id, structure):
8 | return [
9 | "custom_json", {
10 | "required_auths": required_auths,
11 | "required_posting_auths": required_posting_auths,
12 | "id": custom_operation_id,
13 | "json": json.dumps(structure),
14 | }
15 | ]
16 |
--------------------------------------------------------------------------------
/examples/login_app/login_app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request
2 | from steemconnect.client import Client
3 |
4 | app = Flask(__name__)
5 |
6 |
7 | client_id = "your.app"
8 | client_secret = "your_secret"
9 |
10 | c = Client(client_id=client_id, client_secret=client_secret)
11 |
12 |
13 | @app.route('/')
14 | def index():
15 | login_url = c.get_login_url(
16 | "http://localhost:5000/welcome",
17 | "login",
18 | )
19 | return "Login with SteemConnect" % login_url
20 |
21 |
22 | @app.route('/welcome')
23 | def welcome():
24 | c.access_token = request.args.get("access_token")
25 | return "Welcome %s!" % c.me()["name"]
--------------------------------------------------------------------------------
/steemconnect/utils.py:
--------------------------------------------------------------------------------
1 | def requires_access_token(func):
2 | def _decorated(self, *args, **kwargs):
3 | if not self.access_token:
4 | raise ValueError("you need to set your access_token"
5 | " property for this method.")
6 |
7 | return func(self, *args, **kwargs)
8 | return _decorated
9 |
10 |
11 | def requires_client_id_and_secret(func):
12 | def _decorated(self, *args, **kwargs):
13 | if not self.client_id or not self.client_secret:
14 | raise ValueError("you need to set your client_id and client_secret"
15 | " properties for this method.")
16 |
17 | return func(self, *args, **kwargs)
18 | return _decorated
19 |
--------------------------------------------------------------------------------
/docs/hot-signing.rst:
--------------------------------------------------------------------------------
1 |
2 | Hot signing
3 | =================================
4 |
5 | client's hot_sign() method creates a SteemConnect specific URL which you can redirect users and expect them
6 | to broadcast operations are not supported in the api. (transfer, create_delegation, etc.)
7 |
8 | .. function:: hot_sign(self, operation, params, redirect_uri=None):
9 |
10 |
11 | :param operation: String. Operation name. Ex: transfer.
12 | :param params: Dict. Operation data.
13 | :param redirect_uri: String. Optional. If you pass that, SteemConnect will redirect
14 | the user to that URL after the operation succeeds.
15 |
16 | Example: A transfer to emrebeyler with 1 SBD
17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18 |
19 | .. code-block:: python
20 |
21 | url = self.c.hot_sign(
22 | "transfer",
23 | {
24 | "to": "emrebeyler",
25 | "amount": "1 SBD",
26 | "memo": "Donation",
27 | },
28 | redirect_uri="http://localhost"
29 | )
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Emre Yılmaz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/example-flask-app.rst:
--------------------------------------------------------------------------------
1 |
2 | Example flask application
3 | =================================
4 |
5 | This simple flask application redirects the user to steemconnect for the authorization. Once
6 | the user authorizes your app, it calls /me endpoint and gives a warm welcome message with the "name" property of the user.
7 |
8 | .. figure:: https://steemitimages.com/0x0/https://gateway.ipfs.io/ipfs/QmaixSnnT5df3vN9vVAr7KKULL1aBoYRRrBSQ6Mj9qKuhd
9 |
10 |
11 | .. code-block:: python
12 |
13 | from flask import Flask, request
14 | from steemconnect.client import Client
15 |
16 | app = Flask(__name__)
17 |
18 |
19 | client_id = "your.app"
20 | client_secret = "your_secret"
21 |
22 | c = Client(client_id=client_id, client_secret=client_secret)
23 |
24 |
25 | @app.route('/')
26 | def index():
27 | login_url = c.get_login_url(
28 | "http://localhost:5000/welcome",
29 | "login",
30 | )
31 | return "Login with SteemConnect" % login_url
32 |
33 |
34 | @app.route('/welcome')
35 | def welcome():
36 | c.access_token = request.args.get("access_token")
37 | return "Welcome %s!" % c.me()["name"]
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 |
2 |
3 | steemconnect-python-client
4 | ======================================================
5 |
6 | steemconnect-python-client is a simple **yet powerful** library to interact with the Steemconnect. Steemconnect
7 | is a central-single sign on solution for steem based applications. It's considered a secure layer for certain
8 | actions and backed by the Steemit inc.
9 |
10 | Steemconnect implements Oauth2 for the authorization logic.
11 |
12 | What can you do with this client?
13 | ----------
14 |
15 | - Implementing Authorization/Authentication flow through OAuth
16 | - Broadcasting supported operations to the STEEM blockchain with the user of your app.
17 |
18 | Installation
19 | -------------
20 |
21 | steemconnect-python-client requires python3.6 and above. Even though it's easy to make it compatible
22 | with lower versions, it's doesn't have support by design to keep the library simple.
23 |
24 | You can install the library by typing to your console:
25 |
26 | .. code-block:: bash
27 |
28 | $ (sudo) pip install steemconnect
29 |
30 | After that, you can continue with :doc:`/gettingstarted`.
31 |
32 | Documentation Pages
33 | -----------
34 |
35 | .. toctree::
36 | :maxdepth: 5
37 |
38 | gettingstarted
39 | usingtheaccesstoken
40 | broadcast
41 | hot-signing
42 | example-flask-app
43 |
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | flow_tests.py
104 | op_tests.py
--------------------------------------------------------------------------------
/docs/gettingstarted.rst:
--------------------------------------------------------------------------------
1 |
2 | Getting Started
3 | =================================
4 |
5 | Steemconnect supports two different oauth flows for authorization.
6 |
7 | Implicit grant flow
8 | ~~~~~~~~~~~~~~~~~~~~~~~~
9 |
10 | - You create an authorization link with your app's client id and permission scopes.
11 | - User visits the page (steemconnect) and authorizes the application with the given permission scopes.
12 | - steemconnect returns user to the your app with a access token in the query string.
13 |
14 | From that point, you can use this access token to broadcast operations on user's behalf.
15 |
16 | Code authorization flow
17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18 |
19 | access tokens has a short TTL on Oauth standards. Every time a user has their token expired, you have two choices:
20 |
21 | - Re-log the user and get a fresh token
22 | - Add "offline" scope to the required scopes and get a refresh token to refresh the access tokens.
23 |
24 | The second approach is required on some cases for the dApps and you will need to use Code authorization flow this.
25 | When you add the "offline" scope to the required scopes, you will get a code instead of access token.
26 |
27 | With this code, you can get new access tokens till forever. *(As long as the user don't revoke access of your app.)*
28 |
29 | .. note ::
30 | All of these flows are covered at steemconnect-python-client, however have a look to `Steemconnect wiki `_.
31 | to learn more about it.
32 |
33 |
34 | Creating your app on Steemconnect
35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 |
37 |
38 | .. figure:: https://camo.githubusercontent.com/73676c4237e95b24365ef8dfea78119e19237108/68747470733a2f2f7331342e706f7374696d672e63632f6b65716437727268742f53637265656e5f53686f745f323031382d30342d31395f61745f372e35342e35385f504d2e706e67
39 | :width: 600
40 |
41 | You need to `register your app `_ into steemconnect before working with them. This will provide client_id and client_secret information which you will need to interact with the API.
42 |
43 |
44 | Redirecting user
45 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
46 |
47 | .. code-block:: python
48 |
49 | from steemconnect.client import Client
50 |
51 | c = Client(
52 | client_id="app_name",
53 | client_secret="client_secret",
54 | )
55 |
56 |
57 | At this point, we need to redirect the user to steemconnect for they to log in. That requires creating a URL.
58 |
59 | .. code-block:: python
60 |
61 | auth_url = c.get_login_url(
62 | "http://callback.to.your.app",
63 | "login,vote",
64 | )
65 |
66 | - The first parameter is the callback URL when the user authorizes your app on SteemConnect.
67 | - Second parameter is the scopes you need. Available scopes are listed at `Steemconnect wiki `_.
68 |
69 | .. important ::
70 | If you need to use the Code authorization flow, you need to pass **get_refresh_token=True** to this function. Also, "offline" scope is mandatory.
71 |
72 | Once the user authorizes your app, steemconnect will redirect the user to your app with an access token or code depending the flow you choose.
73 | If you get a **code** in the query string, you can use this code to create access tokens for the specified user.
74 |
75 | .. code-block :: python
76 |
77 | c.get_access_token(
78 | code,
79 | )
80 |
81 | Example output
82 |
83 | .. code-block :: javascript
84 |
85 | {
86 | 'access_token': 'access_token_string',
87 | 'expires_in': 604800,
88 | 'username': 'emrebeyler',
89 | 'refresh_token': 'refresh_token_string'
90 | }
91 |
92 | If you use the Implicit grant flow, then you may skip this step.
93 |
94 | Continue with :doc:`/usingtheaccesstoken` to learn what can you do with the access tokens.
95 |
96 |
--------------------------------------------------------------------------------
/docs/usingtheaccesstoken.rst:
--------------------------------------------------------------------------------
1 |
2 | Using access tokens
3 | =================================
4 |
5 | Once you get the access token, you can create a new Client instance with just access_token.
6 |
7 | .. code-block:: python
8 |
9 | c = Client(
10 | access_token="",
11 | )
12 |
13 |
14 | Getting authorized user's information
15 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
16 |
17 | This api call gives information about the authorized user.
18 |
19 | .. code-block:: python
20 |
21 | print(c.me())
22 |
23 |
24 | Updating user profile (metadata)
25 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
26 |
27 |
28 | .. code-block:: python
29 |
30 | metadata = {
31 | "profile": {
32 | "name": "Emre",
33 | "location": "Istanbul, Turkey",
34 | "about": "Developer, STEEM witness.",
35 | "profile_image": "http://foo.bar/image.png"
36 | }
37 | }
38 |
39 | resp = c.update_user_metadata(metadata)
40 |
41 |
42 | Broadcasting operations
43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
44 |
45 | It's possible to
46 |
47 | - vote a post
48 | - create a post/comment
49 | - follow/unfollow/ignore/resteem
50 | - claim reward balance
51 | - delete comment
52 | - create custom jsons
53 |
54 | via steemconnect's broadcast apis.
55 |
56 |
57 | .. note::
58 | All operations live inside the **steemconnect.operations** module. You need to import the corresponding classes before using them.
59 |
60 |
61 | Voting for a post
62 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
63 |
64 | .. code-block:: python
65 |
66 | vote = Vote("account", "author", "permlink", percent)
67 | c.broadcast([vote.to_operation_structure()])
68 |
69 | Creating a comment/post
70 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
71 |
72 | .. code-block:: python
73 |
74 | comment = Comment(
75 | "author",
76 | "permlink",
77 | "body",
78 | title="test title",
79 | json_metadata={"app":"foo/0.0.1"},
80 | )
81 | c.broadcast([comment.to_operation_structure()])
82 |
83 | Creating a comment/post with CommentOptions
84 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
85 |
86 | .. code-block:: python
87 |
88 | comment = Comment(
89 | "author",
90 | "permlink",
91 | "body",
92 | title="test title",
93 | json_metadata={"app":"foo/0.0.1"},
94 | )
95 |
96 | comment_options = CommentOptions(
97 | parent_comment=comment,
98 | allow_curation_rewards=False,
99 | )
100 |
101 | c.broadcast([
102 | comment.to_operation_structure(),
103 | comment_options.to_operation_structure()
104 | ])
105 |
106 |
107 |
108 | Follow an account
109 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
110 |
111 | .. code-block:: python
112 |
113 | follow = Follow("follower", "following")
114 | c.broadcast([follow.to_operation_structure()])
115 |
116 |
117 | Unfollow an account
118 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
119 |
120 | .. code-block:: python
121 |
122 | unfollow = Unfollow("follower", "following")
123 | c.broadcast([unfollow.to_operation_structure()])
124 |
125 | Mute an account
126 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
127 |
128 | .. code-block:: python
129 |
130 | ignore = Mute("follower", "following")
131 | c.broadcast([ignore.to_operation_structure()])
132 |
133 |
134 | Resteem a post
135 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
136 |
137 | .. code-block:: python
138 |
139 | resteem = Resteem("account", "author", "permlink")
140 | c.broadcast([resteem.to_operation_structure()])
141 |
142 |
143 | Claim reward balance
144 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
145 |
146 | .. code-block:: python
147 |
148 | claim_reward_balance = ClaimRewardBalance('account', '0.000 STEEM', '1.500 SBD', '1132.996000 VESTS')
149 | c.broadcast([claim_reward_balance.to_operation_structure()])
150 |
151 | Delete comment
152 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
153 |
154 | .. code-block:: python
155 |
156 | delete_comment = DeleteComment(
157 | "author", "permlink"
158 | )
159 | c.broadcast([delete_comment.to_operation_structure()])
160 |
161 | Create custom jsons
162 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
163 |
164 | .. code-block:: python
165 |
166 | custom_json = CustomJson(
167 | required_auth,
168 | required_posting_auths,
169 | id
170 | json_structure,
171 | )
172 | c.broadcast([custom_json.to_operation_structure()])
173 |
174 |
--------------------------------------------------------------------------------
/steemconnect/client.py:
--------------------------------------------------------------------------------
1 | import json
2 | import urllib.parse
3 |
4 | import requests
5 |
6 | from .defaults import (OAUTH_BASE_URL, SC2_API_BASE_URL)
7 | from .utils import (requires_access_token, requires_client_id_and_secret)
8 |
9 |
10 | class Client:
11 |
12 | def __init__(self, client_id=None, client_secret=None, access_token=None,
13 | oauth_base_url=None, sc2_api_base_url=None):
14 | self.client_id = client_id
15 | self.client_secret = client_secret
16 | self.access_token = access_token
17 | self.oauth_base_url = oauth_base_url or OAUTH_BASE_URL
18 | self.sc2_api_base_url = sc2_api_base_url or SC2_API_BASE_URL
19 |
20 | @property
21 | def headers(self):
22 | return {'Authorization': self.access_token}
23 |
24 | def get_login_url(self, redirect_uri, scope,
25 | get_refresh_token=False):
26 | params = {
27 | "client_id": self.client_id,
28 | "redirect_uri": redirect_uri,
29 | "scope": scope,
30 | }
31 | if get_refresh_token:
32 | params.update({
33 | "response_type": "code",
34 | })
35 |
36 | return urllib.parse.urljoin(
37 | self.oauth_base_url,
38 | "authorize?" + urllib.parse.urlencode(params))
39 |
40 | @requires_client_id_and_secret
41 | def get_access_token(self, code):
42 | post_data = {
43 | "grant_type": "authorization_code",
44 | "code": code,
45 | "client_id": self.client_id,
46 | "client_secret": self.client_secret,
47 | }
48 |
49 | r = requests.post(
50 | urllib.parse.urljoin(self.sc2_api_base_url, "oauth2/token/"),
51 | data=post_data
52 | )
53 |
54 | return r.json()
55 |
56 | @requires_access_token
57 | def me(self):
58 | url = urllib.parse.urljoin(self.sc2_api_base_url, "me/")
59 | r = requests.post(url, headers=self.headers)
60 | return r.json()
61 |
62 | @requires_access_token
63 | def broadcast(self, operations):
64 | url = urllib.parse.urljoin(self.sc2_api_base_url, "broadcast/")
65 | data = {
66 | "operations": operations,
67 | }
68 | headers = self.headers.copy()
69 | headers.update({
70 | "Content-Type": "application/json; charset=utf-8",
71 | })
72 |
73 | r = requests.post(url, headers=headers, data=json.dumps(data))
74 | try:
75 | return r.json()
76 | except ValueError:
77 | return r.content
78 |
79 | @requires_client_id_and_secret
80 | def refresh_access_token(self, refresh_token, scope):
81 | post_data = {
82 | "refresh_token": refresh_token,
83 | "client_id": self.client_id,
84 | "client_secret": self.client_secret,
85 | "scope": scope,
86 | }
87 |
88 | r = requests.post(
89 | urllib.parse.urljoin(self.sc2_api_base_url, "oauth2/token/"),
90 | data=post_data
91 | )
92 |
93 | return r.json()
94 |
95 | def revoke_token(self, access_token):
96 | post_data = {
97 | "access_token": access_token,
98 | }
99 |
100 | r = requests.post(
101 | urllib.parse.urljoin(self.sc2_api_base_url, "oauth2/token/revoke"),
102 | data=post_data
103 | )
104 |
105 | return r.json()
106 |
107 | @requires_access_token
108 | def update_user_metadata(self, metadata):
109 | put_data = {
110 | "user_metadata": metadata,
111 | }
112 | r = requests.put(
113 | urllib.parse.urljoin(self.sc2_api_base_url, "me/"),
114 | data=put_data, headers=self.headers)
115 |
116 | return r.json()
117 |
118 | def hot_sign(self, operation, params, redirect_uri=None):
119 |
120 | if not isinstance(operation, str) or not isinstance(params, dict):
121 | raise ValueError("Invalid Request.")
122 |
123 | base_url = self.sc2_api_base_url.replace("/api", "")
124 |
125 | if redirect_uri:
126 | params.update({"redirect_uri": redirect_uri})
127 |
128 | params = urllib.parse.urlencode(params)
129 | url = urllib.parse.urljoin(base_url, "sign/%s" % operation)
130 | url += "?" + params
131 |
132 | return url
133 |
134 |
135 |
--------------------------------------------------------------------------------
/steemconnect/operations.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from .mixins import CustomJsonMixin
4 |
5 |
6 | class Vote:
7 |
8 | def __init__(self, voter, author, permlink, percent):
9 | self.voter = voter
10 | self.author = author
11 | self.permlink = permlink
12 | self.weight = percent * 100
13 |
14 | def to_operation_structure(self):
15 | return [
16 | "vote", {
17 | "voter": self.voter,
18 | "author": self.author,
19 | "permlink": self.permlink,
20 | "weight": self.weight
21 | }
22 | ]
23 |
24 |
25 | class Comment:
26 |
27 | def __init__(self, author, permlink, body, title=None,
28 | parent_author=None, parent_permlink=None, json_metadata=None):
29 | self.author = author
30 | self.permlink = permlink
31 | self.body = body
32 | self.title = title
33 | self.parent_author = parent_author
34 | self.parent_permlink = parent_permlink
35 | self.json_metadata = json.dumps(json_metadata) if json_metadata else ''
36 |
37 | def to_operation_structure(self):
38 | return [
39 | "comment", {
40 | "parent_author": self.parent_author or "",
41 | "parent_permlink": self.parent_permlink,
42 | "author": self.author,
43 | "permlink": self.permlink,
44 | "title": self.title or "",
45 | "body": self.body,
46 | "json_metadata": self.json_metadata or "",
47 | }
48 | ]
49 |
50 |
51 | class CommentOptions:
52 |
53 | def __init__(self, author=None, permlink=None, extensions=None,
54 | allow_curation_rewards=None, max_accepted_payout=None,
55 | percent_steem_dollars=0, allow_votes=True,
56 | parent_comment=None):
57 | if parent_comment:
58 | self.author = parent_comment.author
59 | self.permlink = parent_comment.permlink
60 | else:
61 | self.author = author
62 | self.permlink = permlink
63 | self.extensions = extensions or []
64 | self.allow_curation_rewards = allow_curation_rewards or True
65 | self.max_accepted_payout = max_accepted_payout or "1000.000 SBD"
66 | self.percent_steem_dollars = percent_steem_dollars
67 | self.allow_votes = allow_votes or True
68 |
69 | def to_operation_structure(self):
70 | return [
71 | "comment_options", {
72 | "author": self.author,
73 | "permlink": self.permlink or "",
74 | "allow_curation_rewards": self.allow_curation_rewards,
75 | "max_accepted_payout": self.max_accepted_payout,
76 | "percent_steem_dollars": self.percent_steem_dollars,
77 | "allow_votes": self.allow_votes,
78 | "extensions": self.extensions,
79 | }
80 | ]
81 |
82 |
83 | class DeleteComment:
84 |
85 | def __init__(self, author, permlink):
86 | self.author = author
87 | self.permlink = permlink
88 |
89 | def to_operation_structure(self):
90 | return [
91 | "delete_comment", {
92 | "author": self.author,
93 | "permlink": self.permlink
94 | }
95 | ]
96 |
97 |
98 | class Follow(CustomJsonMixin):
99 |
100 | def __init__(self, follower, following):
101 | self.follower = follower
102 | self.following = following
103 | self.what = ["blog"]
104 |
105 | def to_operation_structure(self):
106 | return self.get_base_op_structure(
107 | [],
108 | [self.follower],
109 | "follow",
110 | ["follow", {
111 | "follower": self.follower,
112 | "following": self.following,
113 | "what": self.what,
114 | }]
115 | )
116 |
117 |
118 | class Unfollow(Follow):
119 |
120 | def __init__(self, *args, **kwargs):
121 | super().__init__(*args, **kwargs)
122 | self.what = []
123 |
124 |
125 | class Mute(Follow):
126 | def __init__(self, *args, **kwargs):
127 | super().__init__(*args, **kwargs)
128 | self.what = ["ignore"]
129 |
130 |
131 | class Resteem(CustomJsonMixin):
132 |
133 | def __init__(self, account, author, permlink):
134 | self.account = account
135 | self.author = author
136 | self.permlink = permlink
137 |
138 | def to_operation_structure(self):
139 | return self.get_base_op_structure(
140 | [],
141 | [self.account],
142 | "follow",
143 | ["reblog", {
144 | "account": self.account,
145 | "author": self.author,
146 | "permlink": self.permlink,
147 | }]
148 | )
149 |
150 |
151 | class ClaimRewardBalance:
152 |
153 | def __init__(self, account, reward_steem, reward_sbd, reward_vests):
154 | self.account = account
155 | self.reward_steem = reward_steem
156 | self.reward_vests = reward_vests
157 | self.reward_sbd = reward_sbd
158 |
159 | def to_operation_structure(self):
160 | return [
161 | "claim_reward_balance", {
162 | "account": self.account,
163 | "reward_steem": self.reward_steem,
164 | "reward_sbd": self.reward_sbd,
165 | "reward_vests": self.reward_vests,
166 | }
167 | ]
168 |
169 |
170 | class CustomJson(CustomJsonMixin):
171 |
172 | def __init__(self, required_auth, required_posting_auths,
173 | custom_operation_id, structure):
174 | self.required_auth = required_auth
175 | self.required_posting_auths = required_posting_auths
176 | self.custom_operation_id = custom_operation_id
177 | self.structure = structure
178 |
179 | def to_operation_structure(self):
180 | return self.get_base_op_structure(
181 | self.required_auth,
182 | self.required_posting_auths,
183 | self.custom_operation_id,
184 | self.structure
185 | )
186 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/steemconnect-python-client.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/steemconnect-python-client.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/steemconnect-python-client"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/steemconnect-python-client"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # steemconnect-python-client documentation build configuration file, created by
4 | # sphinx-quickstart on Sat Aug 25 21:55:41 2018.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys
16 | import os
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 |
23 | # -- General configuration ------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = []
32 |
33 | # Add any paths that contain templates here, relative to this directory.
34 | templates_path = ['_templates']
35 |
36 | # The suffix of source filenames.
37 | source_suffix = '.rst'
38 |
39 | # The encoding of source files.
40 | #source_encoding = 'utf-8-sig'
41 |
42 | # The master toctree document.
43 | master_doc = 'index'
44 |
45 | # General information about the project.
46 | project = u'steemconnect-python-client'
47 | copyright = u'2018, emrebeyler'
48 |
49 | # The version info for the project you're documenting, acts as replacement for
50 | # |version| and |release|, also used in various other places throughout the
51 | # built documents.
52 | #
53 | # The short X.Y version.
54 | version = '0.0.4'
55 | # The full version, including alpha/beta/rc tags.
56 | release = '0.0.4'
57 |
58 | # The language for content autogenerated by Sphinx. Refer to documentation
59 | # for a list of supported languages.
60 | #language = None
61 |
62 | # There are two options for replacing |today|: either, you set today to some
63 | # non-false value, then it is used:
64 | #today = ''
65 | # Else, today_fmt is used as the format for a strftime call.
66 | #today_fmt = '%B %d, %Y'
67 |
68 | # List of patterns, relative to source directory, that match files and
69 | # directories to ignore when looking for source files.
70 | exclude_patterns = ['_build']
71 |
72 | # The reST default role (used for this markup: `text`) to use for all
73 | # documents.
74 | #default_role = None
75 |
76 | # If true, '()' will be appended to :func: etc. cross-reference text.
77 | #add_function_parentheses = True
78 |
79 | # If true, the current module name will be prepended to all description
80 | # unit titles (such as .. function::).
81 | #add_module_names = True
82 |
83 | # If true, sectionauthor and moduleauthor directives will be shown in the
84 | # output. They are ignored by default.
85 | #show_authors = False
86 |
87 | # The name of the Pygments (syntax highlighting) style to use.
88 | pygments_style = 'sphinx'
89 |
90 | # A list of ignored prefixes for module index sorting.
91 | #modindex_common_prefix = []
92 |
93 | # If true, keep warnings as "system message" paragraphs in the built documents.
94 | #keep_warnings = False
95 |
96 |
97 | # -- Options for HTML output ----------------------------------------------
98 |
99 | # The theme to use for HTML and HTML Help pages. See the documentation for
100 | # a list of builtin themes.
101 | html_theme = 'sphinx_rtd_theme'
102 |
103 | # Theme options are theme-specific and customize the look and feel of a theme
104 | # further. For a list of options available for each theme, see the
105 | # documentation.
106 | html_theme_options = {
107 |
108 | }
109 |
110 | # Add any paths that contain custom themes here, relative to this directory.
111 | #html_theme_path = []
112 |
113 | # The name for this set of Sphinx documents. If None, it defaults to
114 | # " v documentation".
115 | #html_title = None
116 |
117 | # A shorter title for the navigation bar. Default is the same as html_title.
118 | html_short_title = "steemconnect"
119 |
120 | # The name of an image file (relative to this directory) to place at the top
121 | # of the sidebar.
122 | #html_logo = None
123 |
124 | # The name of an image file (within the static path) to use as favicon of the
125 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
126 | # pixels large.
127 | #html_favicon = None
128 |
129 | # Add any paths that contain custom static files (such as style sheets) here,
130 | # relative to this directory. They are copied after the builtin static files,
131 | # so a file named "default.css" will overwrite the builtin "default.css".
132 | html_static_path = ['_static']
133 |
134 | # Add any extra paths that contain custom files (such as robots.txt or
135 | # .htaccess) here, relative to this directory. These files are copied
136 | # directly to the root of the documentation.
137 | #html_extra_path = []
138 |
139 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
140 | # using the given strftime format.
141 | #html_last_updated_fmt = '%b %d, %Y'
142 |
143 | # If true, SmartyPants will be used to convert quotes and dashes to
144 | # typographically correct entities.
145 | #html_use_smartypants = True
146 |
147 | # Custom sidebar templates, maps document names to template names.
148 | #html_sidebars = {}
149 |
150 | # Additional templates that should be rendered to pages, maps page names to
151 | # template names.
152 | #html_additional_pages = {}
153 |
154 | # If false, no module index is generated.
155 | #html_domain_indices = True
156 |
157 | # If false, no index is generated.
158 | #html_use_index = True
159 |
160 | # If true, the index is split into individual pages for each letter.
161 | #html_split_index = False
162 |
163 | # If true, links to the reST sources are added to the pages.
164 | #html_show_sourcelink = True
165 |
166 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
167 | #html_show_sphinx = True
168 |
169 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
170 | #html_show_copyright = True
171 |
172 | # If true, an OpenSearch description file will be output, and all pages will
173 | # contain a tag referring to it. The value of this option must be the
174 | # base URL from which the finished HTML is served.
175 | #html_use_opensearch = ''
176 |
177 | # This is the file name suffix for HTML files (e.g. ".xhtml").
178 | #html_file_suffix = None
179 |
180 | # Output file base name for HTML help builder.
181 | htmlhelp_basename = 'steemconnect-python-clientdoc'
182 |
183 |
184 | # -- Options for LaTeX output ---------------------------------------------
185 |
186 | latex_elements = {
187 | # The paper size ('letterpaper' or 'a4paper').
188 | #'papersize': 'letterpaper',
189 |
190 | # The font size ('10pt', '11pt' or '12pt').
191 | #'pointsize': '10pt',
192 |
193 | # Additional stuff for the LaTeX preamble.
194 | #'preamble': '',
195 | }
196 |
197 | # Grouping the document tree into LaTeX files. List of tuples
198 | # (source start file, target name, title,
199 | # author, documentclass [howto, manual, or own class]).
200 | latex_documents = [
201 | ('index', 'steemconnect-python-client.tex', u'steemconnect-python-client Documentation',
202 | u'emrebeyler', 'manual'),
203 | ]
204 |
205 | # The name of an image file (relative to this directory) to place at the top of
206 | # the title page.
207 | #latex_logo = None
208 |
209 | # For "manual" documents, if this is true, then toplevel headings are parts,
210 | # not chapters.
211 | #latex_use_parts = False
212 |
213 | # If true, show page references after internal links.
214 | #latex_show_pagerefs = False
215 |
216 | # If true, show URL addresses after external links.
217 | #latex_show_urls = False
218 |
219 | # Documents to append as an appendix to all manuals.
220 | #latex_appendices = []
221 |
222 | # If false, no module index is generated.
223 | #latex_domain_indices = True
224 |
225 |
226 | # -- Options for manual page output ---------------------------------------
227 |
228 | # One entry per manual page. List of tuples
229 | # (source start file, name, description, authors, manual section).
230 | man_pages = [
231 | ('index', 'steemconnect-python-client', u'steemconnect-python-client Documentation',
232 | [u'emrebeyler'], 1)
233 | ]
234 |
235 | # If true, show URL addresses after external links.
236 | #man_show_urls = False
237 |
238 |
239 | # -- Options for Texinfo output -------------------------------------------
240 |
241 | # Grouping the document tree into Texinfo files. List of tuples
242 | # (source start file, target name, title, author,
243 | # dir menu entry, description, category)
244 | texinfo_documents = [
245 | ('index', 'steemconnect-python-client', u'steemconnect-python-client Documentation',
246 | u'emrebeyler', 'steemconnect-python-client', 'One line description of project.',
247 | 'Miscellaneous'),
248 | ]
249 |
250 | # Documents to append as an appendix to all manuals.
251 | #texinfo_appendices = []
252 |
253 | # If false, no module index is generated.
254 | #texinfo_domain_indices = True
255 |
256 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
257 | #texinfo_show_urls = 'footnote'
258 |
259 | # If true, do not generate a @detailmenu in the "Top" node's menu.
260 | #texinfo_no_detailmenu = False
261 |
--------------------------------------------------------------------------------
/tests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import responses
4 |
5 | from steemconnect.client import Client
6 | from steemconnect.operations import *
7 |
8 |
9 | class TestClient(unittest.TestCase):
10 |
11 | def test_get_login_url(self):
12 | c = Client(
13 | "client_id"
14 | "client_secret"
15 | )
16 |
17 | self.assertEqual(
18 | c.get_login_url("http://localhost", "login"),
19 | "https://v2.steemconnect.com/oauth2/authorize?client_id"
20 | "=client_idclient_secret&redirect_uri=http%3A%2F%2Flocalh"
21 | "ost&scope=login")
22 |
23 | def test_get_login_url_override_defaults(self):
24 | c = Client(
25 | oauth_base_url="http://foo.bar/oauth2/",
26 | client_id="client_id",
27 | client_secret="client_secret"
28 | )
29 |
30 | self.assertEqual(
31 | c.get_login_url("http://localhost", "login"),
32 | "http://foo.bar/oauth2/authorize?client_id=client_id&"
33 | "redirect_uri=http%3A%2F%2Flocalhost&scope=login")
34 |
35 | @responses.activate
36 | def test_get_access_token(self):
37 |
38 | def request_callback(request):
39 | self.assertEqual(
40 | request.url,
41 | "https://v2.steemconnect.com/api/oauth2/token/")
42 |
43 | self.assertEqual(
44 | request.body,
45 | 'grant_type=authorization_code&code=code&client_id='
46 | 'client_id&client_secret=client_secret'
47 | )
48 | return 200, {}, json.dumps({"access_token": "foo"})
49 |
50 | c = Client(
51 | client_id="client_id",
52 | client_secret="client_secret"
53 | )
54 | responses.add_callback(
55 | responses.POST,
56 | 'https://v2.steemconnect.com/api/oauth2/token/',
57 | callback=request_callback,
58 | )
59 |
60 | c.get_access_token("code")
61 |
62 | @responses.activate
63 | def test_refresh_access_token(self):
64 |
65 | def request_callback(request):
66 | self.assertEqual(
67 | request.url,
68 | "https://v2.steemconnect.com/api/oauth2/token/")
69 |
70 | self.assertEqual(
71 | request.body,
72 | 'refresh_token=refresh_token&client_id=client_id&'
73 | 'client_secret=client_secret&scope=login'
74 | )
75 | return 200, {}, json.dumps({"access_token": "foo"})
76 |
77 | c = Client(
78 | client_id="client_id",
79 | client_secret="client_secret"
80 | )
81 | responses.add_callback(
82 | responses.POST,
83 | 'https://v2.steemconnect.com/api/oauth2/token/',
84 | callback=request_callback,
85 | )
86 |
87 | c.refresh_access_token("refresh_token", "login")
88 |
89 | @responses.activate
90 | def test_me(self):
91 |
92 | def request_callback(request):
93 | self.assertEqual(
94 | request.url,
95 | "https://v2.steemconnect.com/api/me/")
96 |
97 | return 200, {}, json.dumps({"access_token": "foo"})
98 |
99 | c = Client(
100 | access_token="foo"
101 | )
102 | responses.add_callback(
103 | responses.POST,
104 | 'https://v2.steemconnect.com/api/me/',
105 | callback=request_callback,
106 | )
107 |
108 | c.me()
109 |
110 | def test_op_upvote(self):
111 | vote = Vote(
112 | "voter", "author", "permlink", 100)
113 |
114 | self.assertEqual(vote.voter, "voter")
115 | self.assertEqual(vote.author, "author")
116 | self.assertEqual(vote.permlink, "permlink")
117 | self.assertEqual(vote.weight, 10000)
118 |
119 | def test_op_downvote(self):
120 | vote = Vote(
121 | "voter", "author", "permlink", -50)
122 |
123 | self.assertEqual(vote.voter, "voter")
124 | self.assertEqual(vote.author, "author")
125 | self.assertEqual(vote.permlink, "permlink")
126 | self.assertEqual(vote.weight, -5000)
127 |
128 | def test_comment(self):
129 | comment = Comment(
130 | "author",
131 | "permlink",
132 | "body",
133 | title="title",
134 | json_metadata={"app": "testapp/0.0.1", "tags": ["test"]},
135 | )
136 |
137 | self.assertEqual(comment.author, "author")
138 | self.assertEqual(comment.permlink, "permlink")
139 | self.assertEqual(comment.body, "body")
140 | self.assertEqual(comment.title, "title")
141 | self.assertEqual(
142 | comment.json_metadata,
143 | json.dumps({"app": "testapp/0.0.1", "tags": ["test"]}))
144 |
145 | def test_comment_options(self):
146 | comment_options = CommentOptions(
147 | "author",
148 | "permlink",
149 | allow_curation_rewards=False,
150 | )
151 |
152 | self.assertEqual(comment_options.author, "author")
153 | self.assertEqual(comment_options.permlink, "permlink")
154 | self.assertEqual(
155 | comment_options.allow_curation_rewards, False)
156 |
157 | def test_comment_options_comment_injection(self):
158 | comment = Comment(
159 | "author",
160 | "permlink",
161 | "body",
162 | title="title",
163 | json_metadata={"app": "testapp/0.0.1", "tags": ["test"]},
164 | )
165 |
166 | comment_options = CommentOptions(
167 | parent_comment=comment,
168 | allow_curation_rewards=False,
169 | )
170 |
171 | self.assertEqual(comment_options.author, "author")
172 | self.assertEqual(comment_options.permlink, "permlink")
173 | self.assertEqual(
174 | comment_options.allow_curation_rewards, False)
175 |
176 | def test_delete_comment(self):
177 | delete_comment = DeleteComment(
178 | "author", "permlink"
179 | )
180 |
181 | self.assertEqual(delete_comment.author, "author")
182 | self.assertEqual(delete_comment.permlink, "permlink")
183 |
184 | def test_follow(self):
185 | follow = Follow("follower", "following")
186 |
187 | self.assertEqual(follow.follower, "follower")
188 | self.assertEqual(follow.following, "following")
189 | self.assertEqual(follow.what, ["blog",])
190 |
191 | def test_unfollow(self):
192 | unfollow = Unfollow("follower", "following")
193 |
194 | self.assertEqual(unfollow.follower, "follower")
195 | self.assertEqual(unfollow.following, "following")
196 | self.assertEqual(unfollow.what, [])
197 |
198 | def test_mute(self):
199 | ignore = Mute("follower", "following")
200 |
201 | self.assertEqual(ignore.follower, "follower")
202 | self.assertEqual(ignore.following, "following")
203 | self.assertEqual(ignore.what, ["ignore"])
204 |
205 | def test_reblog(self):
206 | resteem = Resteem("account", "author", "permlink")
207 |
208 | self.assertEqual(resteem.account, "account")
209 | self.assertEqual(resteem.author, "author")
210 | self.assertEqual(resteem.permlink, "permlink")
211 |
212 | def test_claim_reward_balance(self):
213 | claim_reward_balance = ClaimRewardBalance(
214 | 'account', '0.000 STEEM', '1.500 SBD', '1132.996000 VESTS'
215 | )
216 |
217 | self.assertEqual(claim_reward_balance.account, "account")
218 | self.assertEqual(claim_reward_balance.reward_steem, "0.000 STEEM")
219 | self.assertEqual(claim_reward_balance.reward_sbd, "1.500 SBD")
220 | self.assertEqual(
221 | claim_reward_balance.reward_vests, "1132.996000 VESTS")
222 |
223 | def test_custom_json(self):
224 |
225 | custom_json = CustomJson(
226 | [],
227 | ["author"],
228 | "testcustomjson",
229 | {"foo": "bar"}
230 | )
231 |
232 | self.assertEqual(custom_json.required_auth, [])
233 | self.assertEqual(custom_json.required_posting_auths, ["author"])
234 | self.assertEqual(custom_json.custom_operation_id, "testcustomjson")
235 | self.assertEqual(custom_json.structure, {"foo": "bar"})
236 |
237 | def test_hot_sign(self):
238 | c = Client()
239 |
240 | sign_url = c.hot_sign(
241 | "transfer",
242 | {
243 | "to": "turbot",
244 | "amount": "0.001 SBD",
245 | "memo": "selam",
246 | },
247 | redirect_uri="http://localhost"
248 | )
249 |
250 | self.assertEqual(sign_url, "https://v2.steemconnect.com/sign/tra"
251 | "nsfer?to=turbot&amount=0.001+SBD&me"
252 | "mo=selam&redirect_uri=http%3A%2F%2F"
253 | "localhost")
254 |
255 | def test_hot_sign_without_redirect_uri(self):
256 | c = Client()
257 |
258 | sign_url = c.hot_sign(
259 | "transfer",
260 | {
261 | "to": "turbot",
262 | "amount": "0.001 SBD",
263 | "memo": "selam",
264 | },
265 | )
266 |
267 | self.assertEqual(sign_url, "https://v2.steemconnect.com/sign/tra"
268 | "nsfer?to=turbot&amount=0.001+SBD&me"
269 | "mo=selam")
270 |
271 |
272 |
273 | if __name__ == '__main__':
274 | unittest.main()
--------------------------------------------------------------------------------