首先访问 https://apps.twitter.com 新建一个 app,这样我们就有了两个数据:
-
Consumer Key
-
Consumer Secret
记得配置 Callback URLs
- 比如开发环境下我将新建一个 /auth_callback
路由用于处理回调,就需要将 http://127.0.0.1:4000/auth_callback
加入 Callback URLs
。
接下来与 twitter 的一切通信都交给 https://github.com/parroty/extwitter 库。
在 mix.exs
文件的 deps
中新增 {:extwitter, "~> 0.9.3"}
,并执行 mix deps.get
安装 extwitter
。
安装完 extwitter
后,按说明在 dev.exs
添加如下配置(记得要重启 Phoenix):
+
+# Configures extwitter oauth
+config :extwitter, :oauth, [
+ consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
+ consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
+]
我们从环境变量中读取 consumer_key
与 consumer_secret
的值,access_token
与 access_token_secret
值暂时不设定,后面在代码中动态设置。
不过等一等,access_token_secret
?我在 User
结构中可是只定义了 access_token
。
显然,我们需要在 User
结构中新增 access_token_secret
字段。
我们要借助 mix ecto.gen.migration
。
在命令行下执行:
$ mix ecto.gen.migration add_access_token_secret_to_users
Generated tweet_bot app
* creating priv/repo/migrations/20181203122738_add_access_token_secret_to_users.exs
打开新建的文件,目前内容如下:
defmodule TweetBot.Repo.Migrations.AddAccessTokenSecretToUsers do
use Ecto.Migration
def change do
end
end
调整如下:
def change do
-
+ alter table(:users) do
+ add(:access_token_secret, :string)
+ end
end
此外,我们还需要调整 user.ex
:
defmodule TweetBot.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field(:access_token, :string)
+ field(:access_token_secret, :string)
field(:from_id, :string)
timestamps()
end
@doc false
def changeset(user, attrs) do
user
- |> cast(attrs, [:from_id, :access_token])
+ |> cast(attrs, [:from_id, :access_token, :access_token_secret])
end
end
接着运行 mix ecto.migrate
来让上述修改生效。
再回到 twitter OAuth 流程,我们需要提交一个回调地址,这样用户登录后,twitter 会跳到该回调 - 并且携带 oauth_token
及 oauth_verifier
,接着我们再提交这两个参数去换取 access_token
及 access_token_secret
。
我们需要在 router.ex
中新增一个路由:
scope "/", TweetBotWeb do
pipe_through :browser # Use the default browser stack
get("/", PageController, :index)
+ get("/auth_callback", AuthController, :callback)
end
接着创建 lib/tweet_bot_web/controllers/auth_controller.ex
文件:
+defmodule TweetBotWeb.AuthController do
+ use TweetBotWeb, :controller
+
+ def callback(conn, _params) do
+ end
+end
我们启动 OAuth 流程的时机是在接收到用户发来 /start
。所以让我们回到 twitter_controller.ex
中,调整代码如下:
def index(conn, %{"message" => %{"from" => %{"id" => from_id}, "text" => text}}) do
- sendMessage(from_id, "你好")
+ case text do
+ "/start" ->
+ token =
+ ExTwitter.request_token(
+ URI.encode_www_form(Routes.auth_url(conn, :callback))
+ )
+
+ {:ok, authenticate_url} = ExTwitter.authenticate_url(token.oauth_token)
+
+ sendMessage(
+ from_id,
+ "请点击链接登录您的 Twitter 账号进行授权:<a href='" <> authenticate_url <> "'>登录 Twitter</a>",
+ parse_mode: "HTML"
+ )
+
+ _ ->
+ sendMessage(from_id, "你好")
+ end
+
json(conn, %{})
end
尝试给测试机器发送 /start
,好一会儿,开发服务器下报告错误:
[error] #PID<0.474.0> running TweetBotWeb.Endpoint terminated
Request: POST /api/twitter
** (exit) an exception was raised:
** (MatchError) no match of right hand side value: {:error, {:failed_connect, [{:to_address, {'api.twitter.com', 443}}, {:inet, [:inet], :etimedout}]}}
(extwitter) lib/extwitter/api/auth.ex:10: ExTwitter.API.Auth.request_token/1
(tweet_bot) lib/tweet_bot_web/controllers/twitter_controller.ex:9: TweetBotWeb.TwitterController.index/2
(tweet_bot) lib/tweet_bot_web/controllers/twitter_controller.ex:1: TweetBotWeb.TwitterController.action/2
(tweet_bot) lib/tweet_bot_web/controllers/twitter_controller.ex:1: TweetBotWeb.TwitterController.phoenix_controller_pipeline/2
(tweet_bot) lib/tweet_bot_web/endpoint.ex:1: TweetBotWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(tweet_bot) lib/tweet_bot_web/endpoint.ex:1: TweetBotWeb.Endpoint.plug_builder_call/2
(tweet_bot) lib/plug/debugger.ex:102: TweetBotWeb.Endpoint."call (overridable 3)"/2
(tweet_bot) lib/tweet_bot_web/endpoint.ex:1: TweetBotWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /Users/sam/Documents/githubRepos/tweet_bot/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
是了,extwitter
要与 twitter 通信,同样需要配置代理。打开 dev.exs
文件,新增如下内容:
+]
+
+# Configures extwitter proxy
+config :extwitter, :proxy, [
+ server: "127.0.0.1",
+ port: 1087
]
重启开发服务器。再发送 /start
给机器人 - 收到登录链接了。
不过且慢点击登录链接。在点击前,我们还需要填充 auth_controller.ex
中的 callback
:
- def callback(conn, _params) do
+ def callback(conn, %{"oauth_token" => oauth_token, "oauth_verifier" => oauth_verifier}) do
+ # 获取 access token
+ {:ok, token} = ExTwitter.access_token(oauth_verifier, oauth_token)
+ IO.inspect(token)
+ text(conn, "授权成功,请关闭此页面")
end
跑一遍流程就会发现,我们已经成功获取到 access_token
与 access_token_secret
了 - 只不过,响应中的名称与我们预想中的不一样,一个是 oauth_token
,一个是 oauth_token_secret
。拿到这俩个数据后,我们就可以以用户的身份发推了:
{:ok, token} = ExTwitter.access_token(oauth_verifier, oauth_token)
- IO.inspect(token)
+ ExTwitter.configure(
+ :process,
+ Enum.concat(
+ ExTwitter.Config.get_tuples,
+ [ access_token: token.oauth_token,
+ access_token_secret: token.oauth_token_secret ]
+ )
+ )
+ ExTwitter.update("I just sign up telegram bot tweet_for_me_bot.")
text(conn, "授权成功,请关闭此页面")
发送 /start
给机器人,点击返回的链接,授权,查看 twitter 主页,有了:I just sign up telegram bot tweet_for_me_bot.
。