{ "info": { "author": "", "author_email": "", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3 :: Only", "Topic :: Communications :: Chat", "Topic :: Communications :: Conferencing" ], "description": "====================\nFlowdock API wrapper\n====================\n\n|PyPI Release Version| |Supported Python Distro|\n\n.. |PyPI Release Version| image:: https://img.shields.io/pypi/v/flowdock-api-wrapper.svg?color=blue&label=PyPI&logo=python&logoColor=white\n.. |Supported Python Distro| image:: https://img.shields.io/pypi/pyversions/flowdock-api-wrapper.svg?color=blue&label=Supported%20Python%20Distro\n\nThis API wrapper aim to **summarize** Flowdock API usage with **intuitive** implementation,\nin order to make development simpler, like creating chatbots, integrating services, and monitoring Flowdock flows.\n\nTo install the wrapper, use ``pip`` or ``pipenv``:\n\n.. code:: sh\n\n $ pip install flowdock-api-wrapper\n\nTo contribute the wrapper, refer to development guide in ``flowdock.py`` comment.\n\nFollowing content focus on introduing the usage of this wrapper;\nbesides, since part of `Flowdock API Document`_ doesn't match current behavior,\nthere are notifications added in each sections.\n\n.. _`Flowdock API Document`: https://www.flowdock.com/api\n\n.. contents:: Contents\n :depth: 2\n\n.. role:: func(literal)\n.. role:: meth(literal)\n.. role:: mod(literal)\n\n\nAuthentication and Channel\n==============================\n\nTo access resources of private channel/flows/external service integrations, Flowdock provides 2 kinds of tokens:\n\n- Personal API token -- As a user to access private channels and flows.\n A user can get personal API token from `API tokens`_ page.\n\n- Flow token -- As an external service integration to access inbox.\n It is generated after adding an integration onto a flow.\n\n.. _`api tokens`: https://www.flowdock.com/account/tokens\n\nHere, we assume keys are stored in a Python file, say :mod:`test_tokens.py`.\n\n.. code:: python\n\n >>> from test_tokens import PERSONAL_API_TOKEN, FLOW_TOKEN\n\nTo connect Flowdock with personal API token, simply invoke :func:`connect` to initialize a \"client\".\n\n.. code:: python\n\n >>> import flowdock\n >>> client = flowdock.connect(token=PERSONAL_API_TOKEN)\n\nWith the client, you can \"join\" the different channels with the same client.\n\n.. code:: python\n\n >>> flow = client(org='hpe', flow='apua-flow')\n >>> private = client(uid=336968)\n\nThe :code:`uid` above is \"user ID\", which can be found in the tail of a private channel URI.\n\n.. _`display name`:\n\nYou can get UID by user's display name (\"Display name\" field in `Edit profile`_ page) as below.\n\n.. _`edit profile`: https://www.flowdock.com/account/edit\n\n.. code:: python\n\n >>> client.get_uid(name='Ray_')\n 336968\n\nFor convenience, you can join a channel in one line:\n\n.. code:: python\n\n >>> flow = flowdock.connect(token=PERSONAL_API_TOKEN, org='hpe', flow='apua-flow')\n >>> private = flowdock.connect(token=PERSONAL_API_TOKEN, uid=336968)\n\nAlso, you can simply give user's display name to get UID and then join the private channel in one line:\n\n.. code:: python\n\n >>> private = flowdock.connect(token=PERSONAL_API_TOKEN, name='Ray_')\n\nConnecting Flowdock with flow token is similar with personal API token;\nthe flow token bound to an individual flow is not required to specify flow.\n\n.. code:: python\n\n >>> external_service = flowdock.connect(flow_token=FLOW_TOKEN)\n\n\nMessaging\n==============================\n\nSend, Edit, and Delete a Message\n----------------------------------------\n\nTo send message in a flow, invoke :meth:`send` of the channel.\n\n.. code:: python\n\n >>> msg_id = flow.send('a message')['id']\n\nTo edit/delete a message, invoke :meth:`edit`/:meth:`delete`;\nto verify the messages, invoke :meth:`get` to get message properties.\n\n.. code:: python\n\n >>> flow.show(msg_id)['content']\n 'a message'\n\n >>> flow.edit(msg_id, 'an edit message')\n >>> flow.show(msg_id)['content']\n 'an edit message'\n\n >>> flow.delete(msg_id)\n >>> flow.show(msg_id)['content']\n ''\n\nThose methods are supported in private channels as well.\n\n.. code:: python\n\n >>> msg_id = private.send('a message')['id']\n >>> private.show(msg_id)['content']\n 'a message'\n\n >>> private.edit(msg_id, 'an edit message')\n >>> private.show(msg_id)['content']\n 'an edit message'\n\n >>> private.delete(msg_id)\n >>> private.show(msg_id)['content']\n ''\n\n\nReply Message onto a Thread\n----------------------------------------\n\nEvery message sent to a flow belongs to a thread:\n\n.. code:: python\n\n >>> msg = flow.send('Thread start')\n >>> thread = msg['thread']\n\nOne can get thread ID of a message by two ways:\n\n.. code:: python\n\n >>> thread_id = msg['thread']['id']\n >>> thread_id = msg['thread_id']\n\nTo send a message onto the thread, invoke :meth:`send` under :meth:`thread`.\n\n.. code:: python\n\n >>> reply = flow.thread(thread_id).send('A message replied')\n\n\nTag a Message\n----------------------------------------\n\nTo send a message with tags in a flow, set keyword argument ``tags`` to :meth:`send`.\n\n.. code:: python\n\n >>> msg_id = flow.send('@team, ref here: http://docs.python.org', tags=['ref'])['id']\n >>> flow.show(msg_id)['tags']\n ['ref', ':user:team', ':url']\n\nTo override the tags of an existing message, set keyword argument ``tags`` to :meth:`edit`.\nIn this case, you don't have to be the author.\n\n.. code:: python\n\n >>> flow.edit(msg_id, tags=['ref', ':user:team', 'important', ':url'])\n >>> flow.show(msg_id)['tags']\n ['ref', ':user:team', 'important', ':url']\n\nYou can edit both content and tags at the same time; in this case, you have to be the author.\n\n.. code:: python\n\n >>> flow.edit(msg_id, '@team, read ref here: http://docs.python.org', tags=['ref', ':user:team', ':url'])\n >>> msg = flow.show(msg_id)\n >>> msg['content']\n '@team, read ref here: http://docs.python.org'\n >>> msg['tags']\n ['ref', ':user:team', ':url']\n\nThe tags prefixed with colon, like ``:user:team`` and ``:url`` above, are used on web page display.\n\nWhen sending a new message, those special tags would be generated by backend;\nin addition, backend eliminates duplicated tags and not change the order of tags.\nAn example of simply adding and removing tags is as below:\n\n.. code:: python\n\n >>> tags = flow.show(msg_id)['tags']\n >>> tags\n ['ref', ':user:team', ':url']\n\n >>> tags += ['ref', 'python']\n >>> flow.edit(msg_id, tags=tags)\n >>> flow.show(msg_id)['tags']\n ['ref', ':user:team', ':url', 'python']\n\n >>> tags.remove('python')\n >>> flow.edit(msg_id, tags=tags)\n >>> flow.show(msg_id)['tags']\n ['ref', ':user:team', ':url']\n\nIt is supported in private channels as well.\n\n.. code:: python\n\n >>> msg_id = private.send('ref here: http://docs.python.org', tags=['ref'])['id']\n >>> private.show(msg_id)['tags']\n [':unread:336968', 'ref', ':url']\n\n >>> private.edit(msg_id, tags=[':unread:336968', 'ref', 'resources', ':url'])\n >>> private.show(msg_id)['tags']\n [':unread:336968', 'ref', 'resources', ':url']\n\n\nUpload and Download a File\n----------------------------------------\n\nTo upload a file in a flow, invoke :meth:`upload` with the file path;\nto download the file, get URI path by :meth:`show` and then invoke :meth:`download`.\n\n.. code:: python\n\n >>> file_path = './README.rst'\n >>> msg_id = flow.upload(file_path)['id']\n >>> msg_content = flow.show(msg_id)['content']\n >>> msg_content['file_name']\n 'README.rst'\n\n >>> uri_path = msg_content['path']\n >>> bin_data = flow.download(uri_path)\n >>> flow.delete(msg_id)\n >>> flow.show(msg_id)\n Traceback (most recent call last):\n ...\n AssertionError: (404, b'{\"message\":\"not found\"}')\n\nThose methods are supported in private channels as well.\n\n.. code:: python\n\n >>> file_path = './README.rst'\n >>> msg_id = private.upload(file_path)['id']\n >>> msg_content = private.show(msg_id)['content']\n >>> msg_content['file_name']\n 'README.rst'\n\n >>> uri_path = msg_content['path']\n >>> bin_data = private.download(uri_path)\n >>> private.delete(msg_id)\n >>> private.show(msg_id)\n Traceback (most recent call last):\n ...\n AssertionError: (404, b'{\"message\":\"not found\"}')\n\n\nList Messages\n----------------------------------------\n\n.. _`List Messages -- Parameters`: https://www.flowdock.com/api/messages\n\nTo list messages with some constraints, invoke :meth:`list` with parameters defined in `List Messages -- Parameters`_.\n\nA basic example is as below. Note that the result is always in *ascending* order.\n\n.. cleanup\n\n >>> for m in flow.list(search='keyword'):\n ... flow.delete(m['id'])\n\n.. code:: python\n\n >>> msg = flow.send('a keyword here')\n\n >>> from time import sleep\n >>> sleep(1) # wait a while\n\n >>> flow.list(search='keyword')[-1]['content']\n 'a keyword here'\n\nAlthough this Flowdock API is flexible to combine parameters, there are still rules hidden behind API.\nAfter trial and error, we summarize two pattern here.\n\n\n1. ``(search keywords) \u222a (match tags in mode) \u2192 skip N \u2192 limit N``\n````````````````````````````````````````````````````````````````````````````````\n\nFor example below, it takes union of search results and tags matching results,\nskip the newest some, then limit the first some. [*]_\n\n.. cleanup\n\n >>> for m in flow.list(search='keyword', tags=['A', 'B'], tag_mode='or'):\n ... flow.delete(m['id'])\n\n.. code:: python\n\n >>> msg1 = flow.send('1. a keyword')\n >>> msg2 = flow.send('2. keywords', tags=['A'])\n >>> msg3 = flow.send('3. more keywords', tags=['A', 'B'])\n\n >>> verify = lambda L: print(*[i['content'][0] for i in L])\n >>> sleep(1)\n\n >>> verify(flow.list(search='keyword'))\n 1\n >>> verify(flow.list(tags=['A', 'B']))\n 3\n >>> verify(flow.list(tags=['A', 'B'], tag_mode='or'))\n 2 3\n >>> verify(flow.list(search='keyword', tags=['A', 'B'], tag_mode='or'))\n 1 2 3\n >>> verify(flow.list(search='keyword', tags=['A', 'B'], tag_mode='or', skip=1))\n 1 2\n >>> verify(flow.list(search='keyword', tags=['A', 'B'], tag_mode='or', limit=1))\n 3\n >>> verify(flow.list(search='keyword', tags=['A', 'B'], tag_mode='or', skip=1, limit=1))\n 2\n\n.. [*] ``tags`` can be either comma seperated string (as document described) or a list of string in fact.\n\n\n2. ``(event type) \u2229 (since ID - until ID) \u2192 sort [desc|asc] \u2192 limit N``\n````````````````````````````````````````````````````````````````````````````````\n\nFor example below, it takes the results of matching event types greater than an ID and less than an ID,\nthen limit the first some or last some. [*]_ [*]_\n\n.. code:: python\n\n >>> file_path = './README.rst'\n >>> msg1 = flow.upload(file_path)\n >>> msg2 = flow.upload(file_path)\n >>> msg3 = flow.upload(file_path)\n >>> msg4 = flow.send('file_path')\n >>> msg5 = flow.upload(file_path)\n\n >>> M = {msg1['id']: 1, msg2['id']: 2, msg3['id']: 3, msg4['id']: 4, msg5['id']:5}\n >>> verify = lambda L: print(*[M[m['id']] for m in L])\n >>> sleep(1)\n\n >>> verify(flow.list(since_id=msg1['id']))\n 2 3 4 5\n >>> verify(flow.list(since_id=msg1['id'], until_id=msg5['id']))\n 2 3 4\n >>> verify(flow.list(event='file', since_id=msg1['id']))\n 2 3 5\n >>> verify(flow.list(event='file', since_id=msg1['id'], limit=1))\n 5\n >>> verify(flow.list(event='file', since_id=msg1['id'], sort='asc', limit=1))\n 2\n\n.. [*] The parameter ``sort`` only works with parameter ``limit`` for changing behavior,\n and will not change the order of result.\n\n.. [*] While delete an uploaded file, the response of \"filtering last some\" becomes incorrect,\n and will be recovered later about 5 minutes.\n\n----\n\nTo list uploaded files, both ways below work:\n\n.. code:: python\n\n >>> msgs = flow.list(tags=':file')\n >>> msgs = flow.list(event='file')\n\nTo list messages contains link or Email, there is a way as below:\n\n.. code:: python\n\n >>> msgs = flow.list(tags=':url')\n\nTo list messages mentioned user with given `display name`_, for example, \"@team\":\n\n.. code:: python\n\n >>> msgs = flow.list(tags='@team')\n\n\nList Threads\n----------------------------------------\n\nIn contrast to listing messages, the result of listing threads is always in *descending* order.\n\nTo list the threads under the flow, invoke :meth:`threads` (plural).\n\n.. code:: python\n\n >>> thread = flow.threads(limit=1)[0]\n\nAPI document list `parameters of listing flow threads`_, but not match the current Flowdock API.\nIn addition to parameter ``limit``, there are only other parameters ``until`` and ``since`` are supported.\n\n.. code:: python\n\n >>> threads = flow.threads(since='2019-01-01T00:00:00Z', until='2019-12-01T00:00:00Z')\n\nTo list messages under a thread, invoke :meth:`list` under :meth:`thread` (singular) with given thread ID.\n\n.. code:: python\n\n >>> msg = flow.thread(thread['id']).list(limit=1)[0]\n\n.. _`parameters of listing flow threads`: `List Flow Threads`_\n\n.. _`List Flow Threads`: https://www.flowdock.com/api/threads#/List\n\n\nIntegration\n==============================\n\n.. image:: https://github.com/apua/flowdock/raw/re-write/Flowdock%20Inbox.png\n :alt: Flowdock Inbox overview\n\nFlowdock can integrate external services, e.g. Trello, onto Flowdock Inbox,\nso that you can track item status, user activities, and discussion on the item.\n\nRefer to Flowdock API documents below to understand the relationship between items and Flowdock threads,\nand activities/discussions of an items.\n\nGetting started:\nhttps://www.flowdock.com/api/integration-getting-started#/getting-started\n\nThe components of an integration message:\nhttps://www.flowdock.com/api/integration-getting-started#/components-of-a-message\n\nMessage types (\"activity\" and \"discussion\"):\nhttps://www.flowdock.com/api/integration-getting-started#/message-types\n\nAuthorize your app with OAuth:\nhttps://www.flowdock.com/api/production-integrations#/oauth2-authorize\n\n\nPresent an External Service Item\n----------------------------------------\n\nThose data maitained on the external servicesa are treated as items, every item has its ID and name, as shown below:\n\n.. code:: python\n\n >>> item_id = 'ITEM-01'\n >>> item = {'title': 'Item 01'}\n\nTo present a user activity or discussion on the item requires define a user first.\n\n.. code:: python\n\n >>> ray = {'name': 'Ray'}\n\nWith given ``thread`` for item and ``author`` for user, you can present an activity or discussion by :meth:`present`.\nTo present an activity, it requires only ``title`` for the activity description;\nto present a discusion, it requires not only ``title`` for the description of discussion itself\n(e.g. \"comment\") but also ``body`` for the discussion content.\n\n.. code:: python\n\n >>> external_service.present(item_id, author=ray, title='created item', thread=item)\n >>> external_service.present(item_id, author=ray, title='commented', body='The comment', thread=item)\n\nThe expected result is as below.\nNote that \"ExternalService\" shown in the figure is the integration name rather than the external service name,\nthus it is recommended to set integration name the same as external service name.\n\n.. image:: https://github.com/apua/flowdock/raw/re-write/basic%20expected%20result.png\n :alt: basic expected result shows the presented item name, a user created item, and discussion\n\nActivities is just like the item history,\ntherefore, each updating item operation should be presented with an activity.\n\nIf a item has been presented before and nothing changed, then it can be presented with only item id,\nfor example, discussion.\n\n.. code:: python\n\n >>> external_service.present(item_id, author=ray, title='commented', body='More comment')\n\nIn the other side, the items, which aren't presented before and don't have both activites and discussion\nafter integration added, are not shown in Flowdock.\n\n\nCheck Presented Items\n----------------------------------------\n\nAfter presenting an activity or discussion, Flowdock API will not return the resource ID of activity or discussion.\nA workaround is checking the latest sent message.\n\n.. code:: python\n\n >>> external_service.present(item_id, author=ray, title='commented', body='No URI returned')\n\nSince there may be newer message has been sent during checking the latest sent message,\nit requires some restrictions to assure the last one is which you sent.\n\nWith no restriction, simply invoke :meth:`list` to get the last one:\n\n.. code:: python\n\n >>> flow.list(limit=1).pop()['body']\n 'No URI returned'\n\nFor example above, which present with a discussion, one can list only last discussion event,\nor list content/body contains the string (obviously it does not work with activity):\n\n.. code:: python\n\n >>> flow.list(event='discussion').pop()['body']\n 'No URI returned'\n >>> flow.list(search='URI').pop()['body']\n 'No URI returned'\n\nThe other workaround is more stable: presenting every thread with optional attribute ``external_url``\nwhich means the item URI actually. With the URI, one can indentify the thread.\nSince it is almost impossible multiple integration presenting the same item,\none can assure the last activity/discussion is sent by themselves.\n\n.. code:: python\n\n >>> uri = f'https://external.service/item/{item_id}'\n >>> item['external_url'] = uri\n >>> external_service.present(item_id, author=ray, title='touched item', thread=item)\n >>> thread = next(t for t in flow.threads() if t['external_url']==uri)\n >>> flow.thread(thread['id']).list(event='activity').pop()['title']\n 'touched item'\n\n\nTag, Reply, and Delete a Presented Item\n----------------------------------------\n\nFlowdock allows user to tag and reply an presented item, just like tag and reply a message.\n\n.. code:: python\n\n >>> disc = flow.list(event='discussion', limit=1).pop()\n >>> flow.edit(disc['id'], tags=['idea']) # tag the discussion\n >>> msg = flow.thread(disc['thread_id']).send('Reply the other idea') # reply the discussion\n\nFlowdock allows user to delete an presented item, too, just like delete a message. [*]_ [*]_\n\n.. code:: python\n\n >>> flow.delete(disc['id'])\n >>> flow.show(disc['id'])\n Traceback (most recent call last):\n ...\n AssertionError: (404, b'{\"message\":\"not found\"}')\n\n.. [*] If all activities/discussions are deleted, the thread of item will be hidden on Flowdock.\n However, it can still found by thread API.\n\n.. [*] It seems anyone in the channel has privilege to delete activities and discussions.\n If so, it is dangerous because that deleted activities or discussions are hard to retrieve again.\n Moreover, in general, there is no need to delete them.\n\n\nConstruct ``author`` and ``thread``\n----------------------------------------\n\nIn `Present an External Service Item`_, an example shows how to construct data,\nwhich has some disadvantages during development:\n\n- Don't know which keys are necessary.\n- Don't remember the name of the keys.\n- May have typo not found until verifying on browser.\n\nOne can know which names are required by :meth:`present` already:\n\n.. code:: python\n\n >>> help(external_service.present)\n Help on function present in module flowdock:\n \n present(id, author, title, body=None, thread=None)\n \n\nHere, this wrapper provides constructors for data structure hints.\n\n.. code:: python\n\n >>> from flowdock import constructors as new\n >>> help(new.author)\n Help on function author in module flowdock:\n \n author(name, avatar=None)\n \n >>> ray = new.author('Ray', avatar='http://somewhere.public/ray.png')\n >>> item = new.thread('Item 01')\n\nFor item description, ``thread`` data structure is complex. See example below. [*]_ [*]_\n\nThe origin data:\n\n.. code:: python\n\n >>> item = {\n ... 'title': 'Item 01',\n ... 'external_url': 'https://external.service/item/ITEM-01',\n ... 'body': 'The detail of the item here....',\n ... 'fields': [{'label': 'a', 'value': '1'}, {'label': 'b', 'value': '2'}],\n ... 'status': {'color': 'green', 'value': 'TODO'},\n ... 'actions': [\n ... {\n ... \"@type\": \"ViewAction\",\n ... \"name\": \"Diff\",\n ... \"url\": \"https://github.com/flowdock/component/pull/42/files\",\n ... },\n ... {\n ... '@type': 'UpdateAction',\n ... 'name': 'Assign to me',\n ... 'target': {\n ... '@type': 'EntryPoint',\n ... 'urlTemplate': 'https://external.service/item/ITEM-01?assign=me',\n ... 'httpMethod': 'POST',\n ... },\n ... },\n ... ],\n ... }\n\nBy constrcutors:\n\n.. code:: python\n\n >>> item_id = 'ITEM-01'\n >>> uri = f'https://external.service/item/{item_id}'\n >>> item = new.thread(\n ... 'Item 01',\n ... external_url = uri,\n ... body = 'The detail of the item here....',\n ... fields = [new.field(label='a', value='1'), new.field(label='b', value='2')],\n ... status = new.status(color='green', value='TODO'),\n ... actions = [\n ... {\n ... \"@type\": \"ViewAction\",\n ... \"name\": \"Diff\",\n ... \"url\": \"https://github.com/flowdock/component/pull/42/files\",\n ... },\n ... {\n ... '@type': 'UpdateAction',\n ... 'name': 'Assign to me',\n ... 'target': {\n ... '@type': 'EntryPoint',\n ... 'urlTemplate': f'{uri}?assign=me',\n ... 'httpMethod': 'POST',\n ... },\n ... },\n ... ],\n ... )\n\nSupported status colors are as below; constructor ``status`` could validate the supported colors.\n\n.. code:: python\n\n >>> item['status'] = new.status(color='not supported color', value='...')\n Traceback (most recent call last):\n ...\n TypeError: got invalid color; supported colors are: ('black', 'blue', 'cyan', 'green', 'grey', 'lime', 'orange', 'purple', 'red', 'yellow')\n\nAbout ``actions``, refer to pages of Flowdock API documents for more information:\n\n - https://www.flowdock.com/api/thread-actions\n - https://www.flowdock.com/api/how-to-create-bidirectional-integrations\n\n.. [*] There is no further constructor for ``actions`` because its data structure is flexible\n and would be bound to external services just like ``external_url``.\n\n.. [*] ``UpdateAction`` defines how Flowdock send HTTP requests to the external service.\n It will not work if external services are in private network;\n in this case, consider ``ViewAction`` for workaround.\n\n\nEvent Monitor\n==============================\n\nBased on `Server-Sent Events`_, `Flowdock streaming API`_ sends JSON content via ``data`` field of events,\nand this API wrapper loads JSON content into Python dict.\n\nTo monitor a flow, invoke :meth:`events` returns an iterator.\nAn example that monitoring a flow and sending a message concurrently as below:\n\n.. code:: python\n\n >>> import threading, time\n ...\n >>> def sleep_and_send_message():\n ... time.sleep(1)\n ... flow.send('1 second later')\n ...\n >>> threading.Thread(target=sleep_and_send_message).start()\n >>> e = next(flow.events())\n >>> e['content']\n '1 second later'\n\nWhat will be sent via `Flowdock streaming API`_ is undocumented and really interesting.\nFor example, one can monitoring whether or not a user is typing.\n\n.. _`flowdock streaming api`: https://www.flowdock.com/api/streaming\n.. _`server-sent events`: https://www.w3.org/TR/2009/WD-eventsource-20090421/#event-stream-interpretation\n\n\nNot Implemented\n==============================\n\nAPI wrapper of some resources are not implemented because they are rarely used. List below:\n\n- Flows\n- A thread\n- Private conversations\n- Users\n- Organizations\n- Sources\n- Invitations\n\n\nAdd Emoji onto a Message\n----------------------------------------\n\nUnfortunately, invoking :meth:`send` and :meth:`edit` to set emoji doesn't work;\nFlowdock doesn't provide API for emoji, either.\n\nA possible solution is emulating browser behavior to login with password, create web socket connection,\nand then communicate with Flowdock server to ask change emoji.\nIt is too complicated, besides, user should not provide their password on chatbot;\nthat's why this library does not provide emoji support, either.\n\n\nRe-thread a Message onto a Thread\n----------------------------------------\n\nLike emoji, invoking :meth:`edit` to re-thread a sent message doesn't work;\nFlowdock doesn't provide API for re-threading, either.\n\n\n", "description_content_type": "text/x-rst", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/apua/flowdock", "keywords": "flowdock", "license": "", "maintainer": "", "maintainer_email": "", "name": "flowdock-api-wrapper", "package_url": "https://pypi.org/project/flowdock-api-wrapper/", "platform": "", "project_url": "https://pypi.org/project/flowdock-api-wrapper/", "project_urls": { "Homepage": "https://github.com/apua/flowdock" }, "release_url": "https://pypi.org/project/flowdock-api-wrapper/1.0/", "requires_dist": [ "requests" ], "requires_python": ">=3.6", "summary": "Flowdock API Wrapper", "version": "1.0" }, "last_serial": 5007083, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "a58a7a0aa7de21323fe52618bbc1d6dd", "sha256": "98bc4064608c8a76b24a0364316a7800d4f555fea6399973343e7c7394b9d63d" }, "downloads": -1, "filename": "flowdock_api_wrapper-1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "a58a7a0aa7de21323fe52618bbc1d6dd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 13074, "upload_time": "2019-03-30T12:58:28", "url": "https://files.pythonhosted.org/packages/36/c5/176760f6879c58d0686234ddf3fdb175eb2cd10372294b9c9c985494f73f/flowdock_api_wrapper-1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f4f45bd92a2a74ca7107560d5d25241e", "sha256": "f3268ce9e117b66de0525d341db3ab2ca1785f1f2a06662f56b404cdc330d43d" }, "downloads": -1, "filename": "flowdock-api-wrapper-1.0.tar.gz", "has_sig": false, "md5_digest": "f4f45bd92a2a74ca7107560d5d25241e", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 20200, "upload_time": "2019-03-30T12:58:30", "url": "https://files.pythonhosted.org/packages/90/67/1d4ecf97845e18a0a7afaeeb656568b971587494013bda3ad3c44f422f61/flowdock-api-wrapper-1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a58a7a0aa7de21323fe52618bbc1d6dd", "sha256": "98bc4064608c8a76b24a0364316a7800d4f555fea6399973343e7c7394b9d63d" }, "downloads": -1, "filename": "flowdock_api_wrapper-1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "a58a7a0aa7de21323fe52618bbc1d6dd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 13074, "upload_time": "2019-03-30T12:58:28", "url": "https://files.pythonhosted.org/packages/36/c5/176760f6879c58d0686234ddf3fdb175eb2cd10372294b9c9c985494f73f/flowdock_api_wrapper-1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f4f45bd92a2a74ca7107560d5d25241e", "sha256": "f3268ce9e117b66de0525d341db3ab2ca1785f1f2a06662f56b404cdc330d43d" }, "downloads": -1, "filename": "flowdock-api-wrapper-1.0.tar.gz", "has_sig": false, "md5_digest": "f4f45bd92a2a74ca7107560d5d25241e", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 20200, "upload_time": "2019-03-30T12:58:30", "url": "https://files.pythonhosted.org/packages/90/67/1d4ecf97845e18a0a7afaeeb656568b971587494013bda3ad3c44f422f61/flowdock-api-wrapper-1.0.tar.gz" } ] }