1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
defmodule Pleroma.Gopher.Server do
use GenServer
require Logger
@gopher Application.get_env(:pleroma, :gopher)
def start_link() do
ip = Keyword.get(@gopher, :ip, {0, 0, 0, 0})
port = Keyword.get(@gopher, :port, 1234)
GenServer.start_link(__MODULE__, [ip, port], [])
end
def init([ip, port]) do
if Keyword.get(@gopher, :enabled, false) do
Logger.info("Starting gopher server on #{port}")
:ranch.start_listener(
:gopher,
100,
:ranch_tcp,
[port: port],
__MODULE__.ProtocolHandler,
[]
)
{:ok, %{ip: ip, port: port}}
else
Logger.info("Gopher server disabled")
{:ok, nil}
end
end
end
defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Repo
@instance Application.get_env(:pleroma, :instance)
@gopher Application.get_env(:pleroma, :gopher)
def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
{:ok, pid}
end
def init(ref, socket, transport, _Opts = []) do
:ok = :ranch.accept_ack(ref)
loop(socket, transport)
end
def info(text) do
"#{text}\tfake\(NULL)\t0\r\n"
end
def link(name, selector, type \\ 1) do
address = Pleroma.Web.Endpoint.host()
port = Keyword.get(@gopher, :port, 1234)
"#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n"
end
def response("") do
info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <>
link("Public Timeline", "/main/public") <>
link("Federated Timeline", "/main/all") <> ".\r\n"
end
def render_activities(activities) do
activities
|> Enum.reverse()
|> Enum.map(fn activity ->
user = User.get_cached_by_ap_id(activity.data["actor"])
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
info(HtmlSanitizeEx.strip_tags(activity.data["object"]["content"]))
end)
|> Enum.join("\r\n")
end
def response("/main/public") do
posts =
ActivityPub.fetch_public_activities(%{"type" => ["Create"], "local_only" => true})
|> render_activities
info("Welcome to the Public Timeline!") <> posts <> ".\r\n"
end
def response("/main/all") do
posts =
ActivityPub.fetch_public_activities(%{"type" => ["Create"]})
|> render_activities
info("Welcome to the Federated Timeline!") <> posts <> ".\r\n"
end
def response("/notices/" <> id) do
with %Activity{} = activity <- Repo.get(Activity, id),
true <- ActivityPub.is_public?(activity) do
activities =
ActivityPub.fetch_activities_for_context(activity.data["context"])
|> render_activities
user = User.get_cached_by_ap_id(activity.data["actor"])
info("Post #{activity.id} by #{user.nickname}") <>
link("More posts by #{user.nickname}", "/users/#{user.nickname}") <> activities <> ".\r\n"
else
_e ->
info("Not public") <> ".\r\n"
end
end
def response("/users/" <> nickname) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
params = %{
"type" => ["Create"],
"actor_id" => user.ap_id
}
activities =
ActivityPub.fetch_public_activities(params)
|> render_activities
info("Posts by #{user.nickname}") <> activities <> ".\r\n"
else
_e ->
info("No such user") <> ".\r\n"
end
end
def loop(socket, transport) do
case transport.recv(socket, 0, 5000) do
{:ok, data} ->
data = String.trim_trailing(data, "\r\n")
transport.send(socket, response(data))
:ok = transport.close(socket)
_ ->
:ok = transport.close(socket)
end
end
end
|