master
Milton Mazzarri 2017-04-08 00:30:17 -05:00
commit bbcb88cdcc
No known key found for this signature in database
GPG Key ID: 9F4193F2B5A558FE
30 changed files with 2159 additions and 0 deletions

10
.editorconfig Normal file
View File

@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.{ex,exs,yml}]
charset = utf-8
indent_style = space
indent_size = 2

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# The directory Mix will write compiled artifacts to.
/_build
# If you run "mix test --cover", coverage assets end up here.
/cover
# The directory Mix downloads your dependencies sources to.
/deps
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
language: elixir
elixir:
- 1.3.0
otp_release:
- 19.0
- 18.0
sudo: false
notifications:
recipients:
- milmazz@gmail.com
env:
- MIX_ENV=test
script:
- "mix do local.hex --force, deps.get, test"
cache:
directories:
- deps

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2017 Milton Mazzarri
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

166
README.md Normal file
View File

@ -0,0 +1,166 @@
# Hunter (alpha)
A Elixir client for [Mastodon](https://github.com/Gargron/mastodon/), a GNU social-compatible micro-blogging service
## Installation
```elixir
def deps do
[{:hunter, "~> 0.1.0"}]
end
```
## Examples
Assumming that you already know your *instance* and your *bearer token* you can do
the following:
```elixir
iex(1)> conn = Hunter.new([base_url: "https://example.com", bearer_token: "123456"])
%Hunter.Client{base_url: "https://example.com",
bearer_token: "123456"}
```
### Getting the current user
```elixir
iex(2)> Hunter.verify_credentials(conn)
%Hunter.Account{acct: "milmazz",
avatar: "https://social.lou.lt/avatars/original/missing.png",
created_at: "2017-04-06T17:43:55.325Z", display_name: "Milton Mazzarri",
followers_count: 2, following_count: 3,
header: "https://social.lou.lt/headers/original/missing.png", id: 8039,
locked: false, note: "", statuses_count: 1,
url: "https://social.lou.lt/@milmazz", username: "milmazz"}
```
### Fetching an account
```elixir
iex(3)> Hunter.account(conn, 8039)
%Hunter.Account{acct: "milmazz",
avatar: "https://social.lou.lt/avatars/original/missing.png",
created_at: "2017-04-06T17:43:55.325Z", display_name: "Milton Mazzarri",
followers_count: 2, following_count: 3,
header: "https://social.lou.lt/headers/original/missing.png", id: 8039,
locked: false, note: "", statuses_count: 1,
url: "https://social.lou.lt/@milmazz", username: "milmazz"}
```
### Getting an account's followers
Returns a list of `Accounts`
```elixir
iex(4)> Hunter.followers(conn, 8039)
[%Hunter.Account{acct: "atmantree@mastodon.club",
avatar: "https://social.lou.lt/system/accounts/avatars/000/008/518/original/7715529d4ceb4554.jpg?1491509276",
created_at: "2017-04-06T20:07:57.119Z", display_name: "Carlos Gustavo Ruiz",
followers_count: 2, following_count: 2,
header: "https://social.lou.lt/system/accounts/headers/000/008/518/original/394f31473de7c64a.png?1491509277",
id: 8518, locked: false,
note: "Programmer, Pythonista, Web Creature, Blogger, C++ and Haskell Fan. Never stop learning, because life never stops teaching.",
statuses_count: 1, url: "https://mastodon.club/@atmantree",
username: "atmantree"},
...
]
```
### Getting who account is following
Returns a list of `Accounts`
```elixir
iex(5)> Hunter.following(conn, 8039)
[%Hunter.Account{acct: "sebasmagri@mastodon.cloud",
avatar: "https://social.lou.lt/system/accounts/avatars/000/007/899/original/19b4d8c1e9d4e68a.jpg?1491498458",
created_at: "2017-04-06T17:07:38.912Z",
display_name: "Sebastián Ramírez Magrí", followers_count: 2,
following_count: 1,
header: "https://social.lou.lt/system/accounts/headers/000/007/899/original/missing.png?1491498458",
id: 7899, locked: false, note: "", statuses_count: 2,
url: "https://mastodon.cloud/@sebasmagri", username: "sebasmagri"},
...]
```
### Following a remote user
```elixir
iex(6)> Hunter.follow_by_uri(conn, "paperswelove@mstdn.io")
%Hunter.Account{acct: "paperswelove@mstdn.io",
avatar: "https://social.lou.lt/system/accounts/avatars/000/007/126/original/60ecc8225809c008.png?1491486258",
created_at: "2017-04-06T13:44:18.281Z", display_name: "Papers We Love",
followers_count: 1, following_count: 0,
header: "https://social.lou.lt/system/accounts/headers/000/007/126/original/missing.png?1491486258",
id: 7126, locked: false,
note: "Building Bridges Between Academia and Industry\r\n\r\n<a href=\"http://paperswelove.org\" rel=\"nofollow noopener\"><span class=\"invisible\">http://</span><span class=\"\">paperswelove.org</span><span class=\"invisible\"></span></a>\r\n<a href=\"http://pwlconf.org\" rel=\"nofollow noopener noopener\"><span class=\"invisible\">http://</span><span class=\"\">pwlconf.org</span><span class=\"invisible\"></span></a>",
statuses_count: 1, url: "https://mstdn.io/@paperswelove",
username: "paperswelove"}
```
### Muting/unmuting an account
```elixir
iex(7)> Hunter.mute(conn, 7899)
%Hunter.Relationship{blocking: false, followed_by: false, following: true,
muting: true, requested: false}
iex(8)> Hunter.unmute(conn, 7899)
%Hunter.Relationship{blocking: false, followed_by: false, following: true,
muting: false, requested: false}
```
### Getting an account's statuses
```elixir
iex(9)> Hunter.statuses(conn, 8039, [limit: 1])
[%Hunter.Status{account: %{"acct" => "milmazz",
"avatar" => "https://social.lou.lt/avatars/original/missing.png",
"created_at" => "2017-04-06T17:43:55.325Z",
"display_name" => "Milton Mazzarri", "followers_count" => 2,
"following_count" => 4,
"header" => "https://social.lou.lt/headers/original/missing.png",
"id" => 8039, "locked" => false, "note" => "", "statuses_count" => 1,
"url" => "https://social.lou.lt/@milmazz", "username" => "milmazz"},
application: %{"name" => "Web", "website" => nil},
content: "<p><a href=\"http://tootplanet.space/@Shutsumon\" class=\"h-card u-url p-nickname mention\">@<span>Shutsumon</span></a> You should read &quot;How to design programs&quot; book <a href=\"http://htdp.org\" rel=\"nofollow noopener\" target=\"_blank\"><span class=\"invisible\">http://</span><span class=\"\">htdp.org</span><span class=\"invisible\"></span></a></p>",
created_at: "2017-04-06T18:28:59.392Z", favourited: nil, favourites_count: 0,
id: 59144, in_reply_to_account_id: 7742, in_reply_to_id: 59042,
media_attachments: [],
mentions: [%{"acct" => "Shutsumon@tootplanet.space", "id" => 7742,
"url" => "http://tootplanet.space/@Shutsumon", "username" => "Shutsumon"}],
reblog: nil, reblogged: nil, reblogs_count: 0, sensitive: false,
spoiler_text: "", tags: [],
uri: "tag:social.lou.lt,2017-04-06:objectId=59144:objectType=Status",
url: "https://social.lou.lt/@milmazz/59144", visibility: "public"}]
```
## TODO
* OAuth2 authentication
- Register client for token-access
- Token authentication for API usage
* Search for accounts or content
* Getting an account's relationship
* Register an application
* Fetching a user's blocks
* Fetching a user's favourites
* Fetching a list of follow requests
* Authorizing or rejecting follow requests
* Support arrays as parameter types
* Getting instance information
* Uploading media attachment
* Fetching a user's mutes
* Fetching a user's notifications
* Getting a single notification
* Clearing notifications
* Fetching user's reports
* Reporting a user
* Getting status context
* Getting a card associated with a status
* Getting who reblogged/favourited a status
## License
Hunter source code is released under Apache 2 License.
Check the [LICENSE](LICENSE) for more information.

30
config/config.exs Normal file
View File

@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure for your application as:
#
# config :hunter, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:hunter, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
import_config "#{Mix.env}.exs"

3
config/dev.exs Normal file
View File

@ -0,0 +1,3 @@
use Mix.Config
config :hunter, hunter_api: Hunter.Api.HTTPClient

3
config/test.exs Normal file
View File

@ -0,0 +1,3 @@
use Mix.Config
config :hunter, hunter_api: Hunter.Api.InMemory

384
lib/hunter.ex Normal file
View File

@ -0,0 +1,384 @@
defmodule Hunter do
@moduledoc """
A Elixir client for Mastodon, a GNU Social compatible micro-blogging service
"""
@hunter_version Mix.Project.config[:version]
@doc """
Retrieve account of authenticated user
## Parameters
* `conn` - connection credentials
"""
@spec verify_credentials(Hunter.Client.t) :: Hunter.Account.t
def verify_credentials(conn), do: Hunter.Account.verify_credentials(conn)
@doc """
Retrieve account
## Parameters
* `conn` - connection credentials
* `id` - account identifier
"""
@spec account(Hunter.Client.t, non_neg_integer) :: Hunter.Account.t
def account(conn, id), do: Hunter.Account.account(conn, id)
@doc """
Get a list of followers
## Parameters
* `conn` - connection credentials
* `id` - account identifier
"""
@spec followers(Hunter.Client.t, non_neg_integer) :: [Hunter.Account.t]
def followers(conn, id), do: Hunter.Account.followers(conn, id)
@doc """
Get a list of followed accounts
## Parameters
* `conn` - connection credentials
* `id` - account identifier
"""
@spec following(Hunter.Client.t, non_neg_integer) :: [Hunter.Account.t]
def following(conn, id), do: Hunter.Account.following(conn, id)
@doc """
Follow a remote user
## Parameters
* `conn` - connection credentials
* `uri` - URI of the remote user, in the format of `username@domain`
"""
@spec follow_by_uri(Hunter.Client.t, URI.t) :: Hunter.Account.t
def follow_by_uri(conn, uri), do: Hunter.Account.follow_by_uri(conn, uri)
@doc """
Register a new OAuth client app on the target instance
## Parameters
* `name`
* `redirect_uri`
* `scopes`
* `website`
"""
@spec create_app(String.t, URI.t, String.t, String.t) :: Hunter.Application.t
def create_app(name, redirect_uri, scopes \\ "read", website \\ nil) do
Hunter.Application.create_app(name, redirect_uri, scopes, website)
end
@doc """
Initializes a client
## Options
* `base_url` - URL of the instance you want to connect to
* `bearer_token` - [String] OAuth access token for your authenticated user
"""
@spec new(Keyword.t) :: Hunter.Client.t
def new(options \\ []), do: Hunter.Client.new(options)
@doc """
User agent of the client
"""
@spec user_agent() :: String.t
def user_agent, do: Hunter.Client.user_agent()
@doc """
Upload a media file
## Parameters
* `conn` - connection credentials
* `file`
"""
@spec upload_media(Hunter.Client.t, Path.t) :: Hunter.Media.t
def upload_media(conn, file), do: Hunter.Media.upload_media(conn, file)
@doc """
Get the relationships of authenticated user towards given other users
## Parameters
* `id` - list of relationship IDs
"""
@spec relationships([non_neg_integer]) :: [Hunter.Relationship.t]
def relationships(ids), do: Hunter.Relationship.relationships(ids)
@doc """
Follow a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@spec follow(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def follow(conn, id), do: Hunter.Relationship.follow(conn, id)
@doc """
Unfollow a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@spec unfollow(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def unfollow(conn, id), do: Hunter.Relationship.unfollow(conn, id)
@doc """
Block a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@spec block(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def block(conn, id), do: Hunter.Relationship.block(conn, id)
@doc """
Unblock a user
* `conn` - connection credentials
* `id` - user identifier
"""
@spec unblock(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def unblock(conn, id), do: Hunter.Relationship.unblock(conn, id)
@doc """
Mute a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@spec mute(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def mute(conn, id), do: Hunter.Relationship.mute(conn, id)
@doc """
Unmute a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@spec unmute(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def unmute(conn, id), do: Hunter.Relationship.unmute(conn, id)
@doc """
Search for content
# Parameters
* `q` - search query
## Options
* `resolve` - whether to resolve non-local accounts
"""
@spec search(String.t, Keyword.t) :: Hunter.Result.t
def search(query, options \\ []), do: Hunter.Result.search(query, options)
@doc """
Create new status
## Parameters
* `conn` - connection credentials
* `text` - [String]
* `in_reply_to_id` - [Integer]
* `media_ids` - [Array<Integer>]
"""
@spec create_status(Hunter.Client.t, String.t, non_neg_integer, [non_neg_integer]) :: Hunter.Status.t
def create_status(conn, text, in_reply_to_id \\ nil, media_ids \\ []) do
Hunter.Status.create_status(conn, text, in_reply_to_id, media_ids)
end
@doc """
Retrieve status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@spec status(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def status(conn, id), do: Hunter.Status.status(conn, id)
@doc """
Destroy status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@spec destroy_status(Hunter.Client.t, non_neg_integer) :: boolean
def destroy_status(conn, id), do: Hunter.Status.destroy_status(conn, id)
@doc """
Reblog a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@spec reblog(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def reblog(conn, id), do: Hunter.Status.reblog(conn, id)
@doc """
Undo a reblog of a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@spec unreblog(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def unreblog(conn, id), do: Hunter.Status.unreblog(conn, id)
@doc """
Favorite a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@spec favourite(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def favourite(conn, id), do: Hunter.Status.favourite(conn, id)
@doc """
Undo a favorite of a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@spec unfavourite(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def unfavourite(conn, id), do: Hunter.Status.unfavourite(conn, id)
@doc """
Get a list of statuses by a user
## Parameters
* `conn` - connection credentials -
* `account_id` - account identifier
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec statuses(Hunter.Client.t, non_neg_integer, Keyword.t) :: [Hunter.Status.t]
def statuses(conn, account_id, options \\ []) do
Hunter.Status.statuses(conn, account_id, options)
end
@doc """
Retrieve statuses from the home timeline
## Parameters
* `conn` - connection credentials
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec home_timeline(Hunter.Client.t, Keyword.t) :: [Hunter.Status.t]
def home_timeline(conn, options \\ []) do
Hunter.Status.home_timeline(conn, options)
end
@doc """
Retrieve statuses from the public timeline
## Parameters
* `conn` - connection credentials
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec public_timeline(Hunter.Client.t, Keyword.t) :: [Hunter.Status.t]
def public_timeline(conn, options \\ []) do
Hunter.Status.public_timeline(conn, options)
end
@doc """
Retrieve statuses from a hashtag
## Parameters
* `conn` - connection credentials
* `hashtag` - string list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec hashtag_timeline(Hunter.Client.t, Keyword.t) :: [Hunter.Status.t]
def hashtag_timeline(conn, hashtag, options \\ []) do
Hunter.Status.hashtag_timeline(conn, hashtag, options)
end
@doc """
Returns Hunter version
"""
@spec version() :: String.t
def version, do: @hunter_version
end

127
lib/hunter/account.ex Normal file
View File

@ -0,0 +1,127 @@
defmodule Hunter.Account do
@moduledoc """
Account entity
This module defines a `Hunter.Account` struct and the main functions
for working with Accounts.
## Fields
* `id` - the ID of the account
* `username` - the username of the account
* `acct` - equals `username` for local users, includes `@domain` for remote ones
* `display_name` - the account's display name
* `note` - Biography of user
* `url` - URL of the user's profile page (can be remote)
* `avatar` - URL to the avatar image
* `header` - URL to the header image
* `locked` - boolean for when the account cannot be followed without waiting for approval first
* `created_at` - the time the account was created
* `followers_count` - the number of followers for the account
* `following_count` - the number of accounts the given account is following
* `statuses_count` - the number of statuses the account has made
"""
@hunter_api Application.get_env(:hunter, :hunter_api)
@type t :: %__MODULE__{
id: non_neg_integer,
username: String.t,
acct: String.t,
display_name: String.t,
note: String.t,
url: URI.t,
avatar: URI.t,
header: URI.t,
locked: String.t,
created_at: String.t,
followers_count: non_neg_integer,
following_count: non_neg_integer,
statuses_count: non_neg_integer
}
@derive [Poison.Encoder]
defstruct [:id,
:username,
:acct,
:display_name,
:note,
:url,
:avatar,
:header,
:locked,
:created_at,
:followers_count,
:following_count,
:statuses_count]
@doc """
Retrieve account of authenticated user
## Parameters
* `conn` - Connection credentials
"""
@spec verify_credentials(Hunter.Client.t) :: Hunter.Account.t
def verify_credentials(conn) do
@hunter_api.verify_credentials(conn)
end
@doc """
Retrieve account
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec account(Hunter.Client.t, non_neg_integer) :: Hunter.Account.t
def account(conn, id) do
@hunter_api.account(conn, id)
end
@doc """
Get a list of followers
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec followers(Hunter.Client.t, non_neg_integer) :: [Hunter.Account.t]
def followers(conn, id) do
@hunter_api.followers(conn, id)
end
@doc """
Get a list of followed accounts
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec following(Hunter.Client.t, non_neg_integer) :: [Hunter.Account.t]
def following(conn, id) do
@hunter_api.following(conn, id)
end
@doc """
Follow a remote user
## Parameters
* `conn` - Connection credentials
* `uri` - URI of the remote user, in the format of username@domain
"""
@spec follow_by_uri(Hunter.Client.t, URI.t) :: Hunter.Account.t
def follow_by_uri(conn, uri) do
@hunter_api.follow_by_uri(conn, uri)
end
end

329
lib/hunter/api.ex Normal file
View File

@ -0,0 +1,329 @@
defmodule Hunter.Api do
## Account
@doc """
Retrieve account of authenticated user
## Parameters
* `conn` - connection credentials
"""
@callback verify_credentials(conn :: Hunter.Client.t) :: Hunter.Account.t
@doc """
Retrieve account
## Parameters
* `conn` - connection credentials
* `id` - account identifier
"""
@callback account(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Account.t
@doc """
Get a list of followers
## Parameters
* `conn` - connection credentials
* `id` - account identifier
"""
@callback followers(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Account.t
@doc """
Get a list of followed accounts
## Parameters
* `conn` - connection credentials
* `id` - account identifier
"""
@callback following(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Account.t
@doc """
Follow a remote user
## Parameters
* `conn` - connection credentials
* `uri` - URI of the remote user, in the format of `username@domain`
"""
@callback follow_by_uri(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Account.t
## Application
@doc """
Register a new OAuth client app on the target instance
## Parameters
* `conn` - connection credentials
* `name`
* `redirect_uri`
* `scopes`
* `website`
"""
@callback create_app(conn :: Hunter.Client.t, name :: String.t, redirect_uri :: URI.t, scopes :: String.t, website :: String.t) :: Hunter.Application.t
@doc """
Upload a media file
## Parameters
* `conn` - connection credentials
* `file` -
"""
@callback upload_media(conn :: Hunter.Client.t, file :: Path.t) :: Hunter.Media.t
## Relationship
@doc """
Get the relationships of authenticated user towards given other users
## Parameters
* `id` - list of relationship IDs
"""
@callback relationships(ids :: [non_neg_integer]) :: [Hunter.Relationship.t]
@doc """
Follow a user
## Parameters
* `conn` - connection credentials
* `id` - user id
"""
@callback follow(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Relationship.t
@doc """
Unfollow a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@callback unfollow(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Relationship.t
@doc """
Block a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@callback block(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Relationship.t
@doc """
Unblock a user
* `conn` - connection credentials
* `id` - user identifier
"""
@callback unblock(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Relationship.t
@doc """
Mute a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@callback mute(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Relationship.t
@doc """
Unmute a user
## Parameters
* `conn` - connection credentials
* `id` - user identifier
"""
@callback unmute(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Relationship.t
## Result
@doc """
Search for content
## Parameters
* `conn` - connection credentials
* `q` - the search query
* `options` - option list
## Options
* `resolve` - whether to resolve non-local accounts
"""
@callback search(Hunter.Client.t, query :: String.t, options :: Keyword.t) :: Hunter.Result.t
## Status
@doc """
Create new status
## Parameters
* `conn` - connection credentials
* `text` - [String]
* `in_reply_to_id` - [Integer]
* `media_ids` - [Array<Integer>]
"""
@callback create_status(conn :: Hunter.Client.t, text :: String.t, in_reply_to_id :: non_neg_integer, media_ids :: [non_neg_integer]) :: Hunter.Status.t
@doc """
Retrieve status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@callback status(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Status
@doc """
Destroy status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@callback destroy_status(conn :: Hunter.Client.t, id :: non_neg_integer) :: boolean
@doc """
Reblog a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@callback reblog(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Status.t
@doc """
Undo a reblog of a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@callback unreblog(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Status.t
@doc """
Favorite a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@callback favourite(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Status.t
@doc """
Undo a favorite of a status
## Parameters
* `conn` - connection credentials
* `id` - status identifier
"""
@callback unfavourite(conn :: Hunter.Client.t, id :: non_neg_integer) :: Hunter.Status.t
@doc """
Get a list of statuses by a user
## Parameters
* `conn` - connection credentials
* `account_id` - account identifier
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@callback statuses(conn :: Hunter.Client.t, account_id :: non_neg_integer, options :: Keyword.t) :: [Hunter.Status.t]
@doc """
Retrieve statuses from the home timeline
## Parameters
* `conn` - connection credentials
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@callback home_timeline(conn :: Hunter.Client.t, options :: Keyword.t) :: [Hunter.Status.t]
@doc """
Retrieve statuses from the public timeline
## Parameters
* `conn` - connection credentials
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@callback public_timeline(conn :: Hunter.Client.t, options :: Keyword.t) :: [Hunter.Status.t]
@doc """
Retrieve statuses from a hashtag
## Parameters
* `conn` - connection credentials
* `hashtag` - list of strings
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@callback hashtag_timeline(conn :: Hunter.Client.t, hashtag :: [String.t], options :: Keyword.t) :: [Hunter.Status]
end

View File

@ -0,0 +1,183 @@
defmodule Hunter.Api.HTTPClient do
@behaviour Hunter.Api
def verify_credentials(%Hunter.Client{base_url: base_url} = conn) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/accounts/verify_credentials", get_headers(conn))
Poison.decode!(body, as: %Hunter.Account{})
end
def account(%Hunter.Client{base_url: base_url} = conn, id) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/accounts/#{id}", get_headers(conn))
Poison.decode!(body, as: %Hunter.Account{})
end
def followers(%Hunter.Client{base_url: base_url} = conn, id) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/accounts/#{id}/followers", get_headers(conn))
Poison.decode!(body, as: [%Hunter.Account{}])
end
def following(%Hunter.Client{base_url: base_url} = conn, id) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/accounts/#{id}/following", get_headers(conn))
Poison.decode!(body, as: [%Hunter.Account{}])
end
def follow_by_uri(%Hunter.Client{base_url: base_url} = conn, uri) do
payload = Poison.encode!(%{uri: uri})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/follows", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Account{})
end
def create_app(%Hunter.Client{base_url: base_url} = conn, name, redirect_uri, scopes, website) do
payload = Poison.encode!(%{client_name: name, redirect_uris: redirect_uri, scopes: scopes, website: website})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/apps", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Application{})
end
def upload_media(%Hunter.Client{base_url: base_url} = conn, file) do
payload = Poison.encode!(%{file: file})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/media", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Media{})
end
def relationships(_ids) do
# :: [Hunter.Relationship.t]
# @return [Hunter::Collection<Hunter::Relationship>]
# perform_request_with_collection(:get, '', array_param(:id, ids), Hunter::Relationship)
HTTPoison.get("/api/v1/accounts/relationships")
# Poison.decode!(body, as: [%Hunter.Relationship{}])
end
def follow(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/accounts/#{id}/follow", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Relationship{})
end
def unfollow(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/accounts/#{id}/unfollow", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Relationship{})
end
def block(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/accounts/#{id}/block", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Relationship{})
end
def unblock(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/accounts/#{id}/unblock", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Relationship{})
end
def mute(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/accounts/#{id}/mute", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Relationship{})
end
def unmute(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/accounts/#{id}/unmute", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Relationship{})
end
def search(_conn, _query, _options) do
# :: Hunter.Result.t
# @return [Hunter::Results] If q is a URL, Hunter will
# attempt to fetch the provided account or status. Otherwise, it
# will do a local account and hashtag search.
# opts = {
# q: query,
# }.merge(options)
# perform_request_with_object(:get, '/api/v1/search', opts, Hunter::Results)
end
def create_status(%Hunter.Client{base_url: base_url} = conn, text, in_reply_to_id, _media_ids) do
payload = Poison.encode!(%{status: text, in_reply_to_id: in_reply_to_id})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/statuses", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Status{})
end
def status(%Hunter.Client{base_url: base_url} = conn, id) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/statuses/#{id}", get_headers(conn))
Poison.decode(body, as: %Hunter.Status{})
end
def destroy_status(%Hunter.Client{base_url: base_url} = conn, id) do
case HTTPoison.delete(base_url <> "/api/v1/statuses/#{id}", get_headers(conn)) do
{:ok, %HTTPoison.Response{status_code: 200}} ->
true
_ ->
false
end
end
def reblog(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/statuses/#{id}/reblog", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Status{})
end
def unreblog(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/statuses/#{id}/unreblog", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Status{})
end
def favourite(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/statuses/#{id}/favourite", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Status{})
end
def unfavourite(%Hunter.Client{base_url: base_url} = conn, id) do
payload = Poison.encode!(%{})
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.post(base_url <> "/api/v1/statuses/#{id}/unfavourite", payload, [{"Content-Type", "application/json"} | get_headers(conn)])
Poison.decode!(body, as: %Hunter.Status{})
end
def statuses(%Hunter.Client{base_url: base_url} = conn, account_id, options) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/accounts/#{account_id}/statuses", get_headers(conn), options)
Poison.decode!(body, as: [%Hunter.Status{}])
end
def home_timeline(%Hunter.Client{base_url: base_url} = conn, options) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/timelines/home", get_headers(conn), options)
Poison.decode!(body, as: [%Hunter.Status{}])
end
def public_timeline(%Hunter.Client{base_url: base_url} = conn, options) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/timelines/public", get_headers(conn), options)
Poison.decode!(body, as: [%Hunter.Status{}])
end
def hashtag_timeline(%Hunter.Client{base_url: base_url} = conn, hashtag, options) do
{:ok, %HTTPoison.Response{body: body, status_code: 200}} = HTTPoison.get(base_url <> "/api/v1/timelines/tag/#{hashtag}", get_headers(conn), options)
Poison.decode!(body, as: [%Hunter.Status{}])
end
## Helpers
defp get_headers(%Hunter.Client{bearer_token: token}) do
[{"Authorization", "Bearer #{token}"}]
end
end

52
lib/hunter/application.ex Normal file
View File

@ -0,0 +1,52 @@
defmodule Hunter.Application do
@moduledoc """
Application entity
This module defines a `Hunter.Application` struct and the main functions
for working with Applications.
## Fields
* `name` - name of the application
* `website` - homepage URL of the application
* `scope` - access scopes
* `redirect_uri` -
## Scopes
* `read` - read data
* `write` - post statuses and upload media for statuses
* `follow` - follow, unfollow, block, unblock
Multiple scopes can be requested during the authorization phase with the `scope` query param
"""
@hunter_api Application.get_env(:hunter, :hunter_api)
@type t :: %__MODULE__{
name: String.t,
redirect_uri: URI.t,
scopes: String.t,
website: URI.t
}
@derive [Poison.Encoder]
defstruct [:name, :redirect_uri, :scopes, :website]
@doc """
Register a new OAuth client app on the target instance
## Parameters
* `conn` - connection credentials
* `name` -
* `redirect_uri` -
* `scopes` -
* `website` -
"""
@spec create_app(Hunter.Client.t, String.t, URI.t, String.t, String.t) :: Hunter.Application.t
def create_app(conn, name, redirect_uri, scopes \\ "read", website \\ nil) do
@hunter_api.create_app(conn, name, redirect_uri, scopes, website)
end
end

29
lib/hunter/attachment.ex Normal file
View File

@ -0,0 +1,29 @@
defmodule Hunter.Attribute do
@moduledoc """
Attribute entity
This module defines a `Hunter.Attribute` struct and the main functions
for working with Attributes.
## Fields
* `id` - ID of the attachment
* `type` - One of: "image", "video", "gifv"
* `url` - URL of the locally hosted version of the image
* `remote_url` - For remote images, the remote URL of the original image
* `preview_url` - URL of the preview image
* `text_url` - Shorter URL for the image, for insertion into text (only present on local images)
"""
@type t :: %__MODULE__{
id: non_neg_integer,
type: String.t,
url: URI.t,
remote_url: URI.t,
preview_url: URI.t,
text_url: URI.t
}
@derive [Poison.Encoder]
defstruct [:id, :type, :url, :remote_url, :preview_url, :text_url]
end

28
lib/hunter/card.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Hunter.Card do
@moduledoc """
Card entity
This module defines a `Hunter.Card` struct and the main functions
for working with Cards
## Fields
* `url`- The url associated with the card
* `title` - The title of the card
* `description` - The card description
* `image` - The image associated with the card, if any
"""
@type t :: %__MODULE__{
url: URI.t,
title: String.t,
description: String.t,
image: String.t
}
@derive [Poison.Encoder]
defstruct [:url, :title, :description, :image]
end

36
lib/hunter/client.ex Normal file
View File

@ -0,0 +1,36 @@
defmodule Hunter.Client do
@moduledoc """
Defines a `Hunter` client
"""
@type t :: %__MODULE__{
base_url: URI.t,
bearer_token: String.t
}
@derive [Poison.Encoder]
defstruct [:base_url, :bearer_token]
@doc """
Initializes a client
## Options
* `base_url` - URL of the instance you want to connect to
* `bearer_token` - [String] OAuth access token for your authenticated user
"""
@spec new(Keyword.t) :: Hunter.Client.t
def new(options \\ []) do
struct(Hunter.Client, options)
end
@doc """
User agent of the client
"""
@spec user_agent() :: String.t
def user_agent do
"Hunter.Elixir/#{Hunter.version}"
end
end

13
lib/hunter/context.ex Normal file
View File

@ -0,0 +1,13 @@
defmodule Hunter.Context do
@moduledoc """
Context entity
## Fields
* `ancestors` - The ancestors of the status in the conversation, as a list of Statuses
* `descendants` - The descendants of the status in the conversation, as a list of Statuses
"""
defstruct [:ancestors, :descendants]
end

24
lib/hunter/instance.ex Normal file
View File

@ -0,0 +1,24 @@
defmodule Hunter.Instance do
@moduledoc """
Instance entity
This module defines a `Hunter.Instance` struct and the main functions
for working with Instances.
## Fields
* `uri` - URI of the current instance
* `title` - The instance's title
* `description` - A description for the instance
* `email` - An email address which can be used to contact the instance administrator
"""
@type t :: %__MODULE__{
uri: URI.t,
title: String.t,
description: String.t,
email: String.t
}
defstruct [:uri, :title, :description, :email]
end

28
lib/hunter/media.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Hunter.Media do
@hunter_api Application.get_env(:hunter, :hunter_api)
@type t :: %__MODULE__{
id: non_neg_integer,
url: URI.t,
preview_url: URI.t,
type: String.t
}
@derive [Poison.Encoder]
defstruct [:id, :url, :preview_url, :type]
@doc """
Upload a media file
## Parameters
* `conn` - Connection credentials
* `file` - [HTTP::FormData::File]
"""
@spec upload_media(Hunter.Client.t, Path.t) :: Hunter.Media.t
def upload_media(conn, file) do
@hunter_api.upload_media(conn, file)
end
end

23
lib/hunter/mention.ex Normal file
View File

@ -0,0 +1,23 @@
defmodule Hunter.Mention do
@moduledoc """
Mention entity
## Fields
* `url` - URL of user's profile (can be remote)
* `username` - The username of the account
* `acct` - Equals `username` for local users, includes `@domain` for remote ones
* `id` - Account ID
"""
@type t :: %__MODULE__{
url: URI.t,
username: String.t,
acct: String.t,
id: non_neg_integer
}
@derive [Poison.Encoder]
defstruct [:url, :username, :acct, :id]
end

View File

@ -0,0 +1,27 @@
defmodule Hunter.Notification do
@moduledoc """
Notification entity
This module defines a `Hunter.Notification` struct and the main functions
for working with Notifications.
## Fields
* `id` - The notification ID
* `type` - One of: "mention", "reblog", "favourite", "follow"
* `created_at` - The time the notification was created
* `account` - The `Hunter.Account` sending the notification to the user
* `status` - The `Hunter.Status` associated with the notification, if applicable
"""
@type t :: %__MODULE__{
id: String.t,
type: String.t,
created_at: String.t,
account: Hunter.Account.t,
status: Hunter.Status.t
}
@derive [Poison.Encoder]
defstruct [:id, :type, :created_at, :account, :status]
end

124
lib/hunter/relationship.ex Normal file
View File

@ -0,0 +1,124 @@
defmodule Hunter.Relationship do
@moduledoc """
Relationship entity
This module defines a `Hunter.Relationship` struct and the main functions
for working with Relationship.
## Fields
* `following` - Whether the user is currently following the account
* `followed_by` - Whether the user is currently being followed by the account
* `blocking` - Whether the user is currently blocking the account
* `muting` - Whether the user is currently muting the account
* `requested` - Whether the user has requested to follow the account
"""
@hunter_api Application.get_env(:hunter, :hunter_api)
@type t :: %__MODULE__{
following: boolean,
followed_by: boolean,
blocking: boolean,
muting: boolean,
requested: boolean
}
@derive [Poison.Encoder]
defstruct [:following, :followed_by, :blocking, :muting, :requested]
@doc """
Get the relationships of authenticated user towards given other users
## Parameters
* `id` - list of relationship IDs
"""
@spec relationships([non_neg_integer]) :: [Hunter.Relationship.t]
def relationships(ids) do
@hunter_api.relationships(ids)
end
@doc """
Follow a user
## Parameters
* `conn` - Connection credentials
* `id` - user id
"""
@spec follow(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def follow(conn, id) do
@hunter_api.follow(conn, id)
end
@doc """
Unfollow a user
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec unfollow(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def unfollow(conn, id) do
@hunter_api.unfollow(conn, id)
end
@doc """
Block a user
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec block(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def block(conn, id) do
@hunter_api.block(conn, id)
end
@doc """
Unblock a user
* `conn` - Connection credentials
* `id`
"""
@spec unblock(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def unblock(conn, id) do
@hunter_api.unblock(conn, id)
end
@doc """
Mute a user
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec mute(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def mute(conn, id) do
@hunter_api.mute(conn, id)
end
@doc """
Unmute a user
## Parameters
* `conn` - Connection credentials
* `id`
"""
@spec unmute(Hunter.Client.t, non_neg_integer) :: Hunter.Relationship.t
def unmute(conn, id) do
@hunter_api.unmute(conn, id)
end
end

15
lib/hunter/report.ex Normal file
View File

@ -0,0 +1,15 @@
defmodule Hunter.Report do
@moduledoc """
Report entity
This module defines a `Hunter.Report` struct and the main functions
for working with Reports.
## Fields
* `id` - The ID of the report
* `action_taken` - The action taken in response to the report
"""
defstruct [:id, :action_taken]
end

42
lib/hunter/result.ex Normal file
View File

@ -0,0 +1,42 @@
defmodule Hunter.Result do
@moduledoc """
Result entity
## Fields
* `accounts` - list of matched `Hunter.Account`
* `statuses` - list of matched `Hunter.Status`
* `hashtags` - list of matched hashtags, as strings
"""
@hunter_api Application.get_env(:hunter, :hunter_api)
@type t :: %__MODULE__{
accounts: [Hunter.Account.t],
statuses: [Hunter.Status.t],
hashtags: [String.t]
}
@derive [Poison.Encoder]
defstruct accounts: [],
statuses: [],
hashtags: []
@doc """
Search for content
# Parameters
* `conn` - Connection credentials
* `q` - [String] The search query
## Options
* `resolve` - [Boolean] Whether to resolve non-local accounts
"""
@spec search(Hunter.Client.t, String.t, Keyword.t) :: Hunter.Result.t
def search(conn, query, options \\ []) do
@hunter_api.search(conn, query, options)
end
end

255
lib/hunter/status.ex Normal file
View File

@ -0,0 +1,255 @@
defmodule Hunter.Status do
@moduledoc """
Status entity
## Fields
* `id` - The ID of the status
* `uri` - A Fediverse-unique resource ID
* `url` - URL to the status page (can be remote)
* `account` - The `Hunter.Account` which posted the status
* `in_reply_to_id` - `nil` or the ID of the status it replies to
* `in_reply_to_account_id` - `nil` or the ID of the account it replies to
* `reblog` - `nil` or the reblogged `Hunter.Status`
* `content` - Body of the status; this will contain HTML (remote HTML already sanitized)
* `created_at` - The time the status was created
* `reblogs_count` - The number of reblogs for the status
* `favourites_count` - The number of favourites for the status
* `reblogged` - Whether the authenticated user has reblogged the status
* `favourited` - Whether the authenticated user has favourited the status
* `sensitive` - Whether media attachments should be hidden by default
* `spoiler_text` - If not empty, warning text that should be displayed before the actual content
* `visibility` - One of: `public`, `unlisted`, `private`, `direct`
* `media_attachments` - A list of `Hunter.Attachment`
* `mentions` - A list of `Hunter.Mention`
* `tags` - A list of `Hunter.Tag`
* `application` - `Hunter.Application` from which the status was posted
"""
@hunter_api Application.get_env(:hunter, :hunter_api)
@type t :: %__MODULE__{
id: non_neg_integer,
uri: URI.t,
url: URI.t,
account: Hunter.Account.t,
in_reply_to_id: non_neg_integer,
reblog: Hunter.Status.t | nil,
content: String.t,
created_at: String.t,
reblogs_count: non_neg_integer,
favourites_count: non_neg_integer,
reblogged: boolean,
favourited: boolean,
sensitive: boolean,
spoiler_text: String.t,
media_attachments: [Hunter.Attachment.t],
mentions: [Hunter.Mention.t],
tags: [Hunter.Tag.t],
application: Hunter.Application.t
}
@derive [Poison.Encoder]
defstruct [:id,
:uri,
:url,
:account,
:in_reply_to_id,
:in_reply_to_account_id,
:reblog,
:content,
:created_at,
:reblogs_count,
:favourites_count,
:reblogged,
:favourited,
:sensitive,
:spoiler_text,
:visibility,
:media_attachments,
:mentions,
:tags,
:application]
@doc """
Create new status
## Parameters
* `conn` - connection credentials
* `text` - [String]
* `in_reply_to_id` - [Integer]
* `media_ids` - [Array<Integer>]
"""
@spec create_status(Hunter.Client.t, String.t, non_neg_integer, [non_neg_integer]) :: Hunter.Status.t
def create_status(conn, text, in_reply_to_id \\ nil, media_ids \\ []) do
@hunter_api.create_status(conn, text, in_reply_to_id, media_ids)
end
@doc """
Retrieve status
## Parameters
* `conn` - Connection credentials
* `id` [Integer]
"""
@spec status(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def status(conn, id) do
@hunter_api.status(conn, id)
end
@doc """
Destroy status
## Parameters
* `conn` - Connection credentials
* `id` [Integer]
"""
@spec destroy_status(Hunter.Client.t, non_neg_integer) :: boolean
def destroy_status(conn, id) do
@hunter_api.destroy_status(conn, id)
end
@doc """
Reblog a status
## Parameters
* `conn` - Connection credentials
* `id` - [Integer]
"""
@spec reblog(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def reblog(conn, id) do
@hunter_api.reblog(conn, id)
end
@doc """
Undo a reblog of a status
## Parameters
* `conn` - Connection credentials
* `id` - [Integer]
"""
@spec unreblog(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def unreblog(conn, id) do
@hunter_api.unreblog(conn, id)
end
@doc """
Favorite a status
## Parameters
* `conn` - Connection credentials
* `id` - [Integer]
"""
@spec favourite(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def favourite(conn, id) do
@hunter_api.favourite(conn, id)
end
@doc """
Undo a favorite of a status
## Parameters
* `conn` - Connection credentials
* `id` - [Integer]
"""
@spec unfavourite(Hunter.Client.t, non_neg_integer) :: Hunter.Status.t
def unfavourite(conn, id) do
@hunter_api.unfavourite(conn, id)
end
@doc """
Get a list of statuses by a user
## Parameters
* `conn` - Connection credentials
* `account_id` [Integer]
* `options` - options
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec statuses(Hunter.Client.t, non_neg_integer, Keyword.t) :: [Hunter.Status.t]
def statuses(conn, account_id, options \\ []) do
@hunter_api.statuses(conn, account_id, options)
end
@doc """
Retrieve statuses from the home timeline
## Parameters
* `conn` - Connection credentials
* `options` - option list
## Options
* `conn` - Connection credentials
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec home_timeline(Hunter.Client.t, Keyword.t) :: [Hunter.Status.t]
def home_timeline(conn, options \\ []) do
@hunter_api.home_timeline(conn, options)
end
@doc """
Retrieve statuses from the public timeline
## Parametes
* `conn` - Connection credentials
* `options` - option list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec public_timeline(Hunter.Client.t, Keyword.t) :: [Hunter.Status.t]
def public_timeline(conn, options \\ []) do
@hunter_api.public_timeline(conn, options)
end
@doc """
Retrieve statuses from a hashtag
## Parameters
* `conn` - connection credentials
* `hashtag` - string list
## Options
* `max_id` - [Integer]
* `since_id` - [Integer]
* `limit` - [Integer]
"""
@spec hashtag_timeline(Hunter.Client.t, Keyword.t) :: [Hunter.Status.t]
def hashtag_timeline(conn, hashtag, options \\ []) do
@hunter_api.hashtag_timeline(conn, hashtag, options)
end
end

18
lib/hunter/tag.ex Normal file
View File

@ -0,0 +1,18 @@
defmodule Hunter.Tag do
@moduledoc """
Tag entity
## Fields
* `name` - The hashtag, not including the preceding `#`
* `url` - The URL of the hashtag
"""
@type t :: %__MODULE__{
name: String.t,
url: URI.t
}
defstruct [:name, :url]
end

45
mix.exs Normal file
View File

@ -0,0 +1,45 @@
defmodule Hunter.Mixfile do
use Mix.Project
def project do
[app: :hunter,
version: "0.1.0",
elixir: "~> 1.3",
docs: docs(),
package: package(),
source_url: "https://github.com/milmazz/hunter",
description: "Elixir wrapper for Mastodon API",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
elixirc_paths: elixirc_paths(Mix.env),
deps: deps()]
end
# Configuration for the OTP application
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger, :httpoison]]
end
defp deps do
[{:httpoison, "~> 0.10.0"},
{:poison, "~> 3.0"},
{:ex_doc, "~> 0.14", only: :dev, runtime: false}]
end
defp package do
[licenses: ["Apache 2.0"],
maintainers: ["Milton Mazzarri"],
links: %{"GitHub" => "https://github.com/milmazz/hunter"}]
end
defp docs do
[extras: ["README.md"], main: "readme"]
end
# Specifies which paths to compile per environment
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
end

10
mix.lock Normal file
View File

@ -0,0 +1,10 @@
%{"certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], []},
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"hackney": {:hex, :hackney, "1.6.6", "5564b4695d48fd87859e9df77a7fa4b4d284d24519f0cd7cc898f09e8fbdc8a3", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"httpoison": {:hex, :httpoison, "0.10.0", "4727b3a5e57e9a4ff168a3c2883e20f1208103a41bccc4754f15a9366f49b676", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, optional: false]}]},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}}

104
test/support/in_memory.ex Normal file
View File

@ -0,0 +1,104 @@
defmodule Hunter.Api.InMemory do
@behaviour Hunter.Api
def verify_credentials(_) do
%Hunter.Account{}
end
def account(_, _) do
%Hunter.Account{}
end
def followers(_, _) do
[%Hunter.Account{}]
end
def following(_, _) do
[%Hunter.Account{}]
end
def follow_by_uri(_, _) do
%Hunter.Account{}
end
def create_app(_, _, _, _, _) do
%Hunter.Application{}
end
def upload_media(_, _) do
%Hunter.Media{}
end
def relationships(_) do
end
def follow(_, _) do
%Hunter.Relationship{}
end
def unfollow(_, _) do
%Hunter.Relationship{}
end
def block(_, _) do
%Hunter.Relationship{}
end
def unblock(_, _) do
%Hunter.Relationship{}
end
def mute(_, _) do
%Hunter.Relationship{}
end
def unmute(_, _) do
%Hunter.Relationship{}
end
def search(_, _, _) do
end
def create_status(_, _, _, _) do
end
def status(_, _) do
%Hunter.Status{}
end
def destroy_status(_, _) do
true
end
def reblog(_, _) do
%Hunter.Status{}
end
def unreblog(_, _) do
%Hunter.Status{}
end
def favourite(_, _) do
%Hunter.Status{}
end
def unfavourite(_, _) do
%Hunter.Status{}
end
def statuses(_, _, _) do
[%Hunter.Status{}]
end
def home_timeline(_, _) do
[%Hunter.Status{}]
end
def public_timeline(_, _) do
end
def hashtag_timeline(_, _, _) do
end
end

1
test/test_helper.exs Normal file
View File

@ -0,0 +1 @@
ExUnit.start()