user.ex 36.4 KB
Newer Older
1001
1002
1003
1004
1005
  # helper to handle the block given only an actor's AP id
  def block(blocker, %{ap_id: ap_id}) do
    block(blocker, User.get_by_ap_id(ap_id))
  end

lain's avatar
lain committed
1006
1007
1008
1009
  def unblock(blocker, %{ap_id: ap_id}) do
    info_cng =
      blocker.info
      |> User.Info.remove_from_block(ap_id)
lain's avatar
lain committed
1010

lain's avatar
lain committed
1011
1012
1013
1014
1015
    cng =
      change(blocker)
      |> put_embed(:info, info_cng)

    update_and_set_cache(cng)
lain's avatar
lain committed
1016
1017
  end

1018
  def mutes?(nil, _), do: false
1019
  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
1020

lain's avatar
lain committed
1021
  def blocks?(user, %{ap_id: ap_id}) do
lain's avatar
lain committed
1022
1023
    blocks = user.info.blocks
    domain_blocks = user.info.domain_blocks
eal's avatar
eal committed
1024
    %{host: host} = URI.parse(ap_id)
eal's avatar
eal committed
1025
1026
1027
1028
1029

    Enum.member?(blocks, ap_id) ||
      Enum.any?(domain_blocks, fn domain ->
        host == domain
      end)
eal's avatar
eal committed
1030
1031
  end

1032
1033
1034
  def muted_users(user),
    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))

1035
1036
1037
  def blocked_users(user),
    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))

eal's avatar
eal committed
1038
  def block_domain(user, domain) do
lain's avatar
lain committed
1039
1040
1041
    info_cng =
      user.info
      |> User.Info.add_to_domain_block(domain)
eal's avatar
eal committed
1042

lain's avatar
lain committed
1043
1044
1045
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
1046
1047

    update_and_set_cache(cng)
eal's avatar
eal committed
1048
1049
1050
  end

  def unblock_domain(user, domain) do
lain's avatar
lain committed
1051
1052
1053
    info_cng =
      user.info
      |> User.Info.remove_from_domain_block(domain)
eal's avatar
eal committed
1054

lain's avatar
lain committed
1055
1056
1057
    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
1058
1059

    update_and_set_cache(cng)
lain's avatar
lain committed
1060
1061
  end

Maxim Filippov's avatar
Maxim Filippov committed
1062
1063
  def maybe_local_user_query(query, local) do
    if local, do: local_user_query(query), else: query
1064
1065
  end

Maxim Filippov's avatar
Maxim Filippov committed
1066
  def local_user_query(query \\ User) do
1067
    from(
Maxim Filippov's avatar
Maxim Filippov committed
1068
      u in query,
1069
1070
1071
      where: u.local == true,
      where: not is_nil(u.nickname)
    )
lain's avatar
lain committed
1072
1073
  end

1074
1075
1076
  def active_local_user_query do
    from(
      u in local_user_query(),
1077
      where: fragment("not (?->'deactivated' @> 'true')", u.info)
1078
1079
1080
1081
    )
  end

  def moderator_user_query do
kaniini's avatar
kaniini committed
1082
1083
1084
1085
1086
1087
1088
    from(
      u in User,
      where: u.local == true,
      where: fragment("?->'is_moderator' @> 'true'", u.info)
    )
  end

scarlett's avatar
scarlett committed
1089
  def deactivate(%User{} = user, status \\ true) do
lain's avatar
lain committed
1090
    info_cng = User.Info.set_activation_status(user.info, status)
lain's avatar
lain committed
1091
1092
1093
1094

    cng =
      change(user)
      |> put_embed(:info, info_cng)
lain's avatar
lain committed
1095
1096

    update_and_set_cache(cng)
lain's avatar
lain committed
1097
  end
lain's avatar
lain committed
1098

lain's avatar
lain committed
1099
  def delete(%User{} = user) do
lain's avatar
lain committed
1100
1101
1102
    {:ok, user} = User.deactivate(user)

    # Remove all relationships
lain's avatar
lain committed
1103
1104
    {:ok, followers} = User.get_followers(user)

lain's avatar
lain committed
1105
    followers
lain's avatar
lain committed
1106
    |> Enum.each(fn follower -> User.unfollow(follower, user) end)
lain's avatar
lain committed
1107
1108

    {:ok, friends} = User.get_friends(user)
lain's avatar
lain committed
1109

lain's avatar
lain committed
1110
    friends
lain's avatar
lain committed
1111
    |> Enum.each(fn followed -> User.unfollow(user, followed) end)
lain's avatar
lain committed
1112

lain's avatar
lain committed
1113
    query = from(a in Activity, where: a.actor == ^user.ap_id)
lain's avatar
lain committed
1114
1115

    Repo.all(query)
lain's avatar
lain committed
1116
    |> Enum.each(fn activity ->
lain's avatar
lain committed
1117
      case activity.data["type"] do
lain's avatar
lain committed
1118
        "Create" ->
1119
          ActivityPub.delete(Object.normalize(activity.data["object"]))
lain's avatar
lain committed
1120
1121
1122
1123

        # TODO: Do something with likes, follows, repeats.
        _ ->
          "Doing nothing"
lain's avatar
lain committed
1124
1125
1126
      end
    end)

1127
    {:ok, user}
lain's avatar
lain committed
1128
  end
1129

lain's avatar
lain committed
1130
  def html_filter_policy(%User{info: %{no_rich_text: true}}) do
kaniini's avatar
kaniini committed
1131
1132
1133
    Pleroma.HTML.Scrubber.TwitterText
  end

1134
1135
1136
  @default_scrubbers Pleroma.Config.get([:markup, :scrub_policy])

  def html_filter_policy(_), do: @default_scrubbers
kaniini's avatar
kaniini committed
1137

1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
  def fetch_by_ap_id(ap_id) do
    ap_try = ActivityPub.make_user_from_ap_id(ap_id)

    case ap_try do
      {:ok, user} ->
        user

      _ ->
        case OStatus.make_user(ap_id) do
          {:ok, user} -> user
          _ -> {:error, "Could not fetch by AP id"}
        end
    end
  end

1153
  def get_or_fetch_by_ap_id(ap_id) do
1154
1155
1156
    user = get_by_ap_id(ap_id)

    if !is_nil(user) and !User.needs_update?(user) do
1157
1158
      user
    else
1159
1160
1161
      # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
      should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])

1162
      user = fetch_by_ap_id(ap_id)
lain's avatar
lain committed
1163

1164
      if should_fetch_initial do
1165
1166
1167
        with %User{} = user do
          {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
        end
1168
      end
1169
1170

      user
1171
1172
1173
    end
  end

1174
  def get_or_create_instance_user do
1175
1176
1177
    relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"

    if user = get_by_ap_id(relay_uri) do
1178
1179
1180
      user
    else
      changes =
lain's avatar
lain committed
1181
        %User{info: %User.Info{}}
1182
        |> cast(%{}, [:ap_id, :nickname, :local])
1183
        |> put_change(:ap_id, relay_uri)
1184
1185
        |> put_change(:nickname, nil)
        |> put_change(:local, true)
1186
        |> put_change(:follower_address, relay_uri <> "/followers")
1187
1188
1189
1190
1191
1192

      {:ok, user} = Repo.insert(changes)
      user
    end
  end

1193
  # AP style
lain's avatar
lain committed
1194
  def public_key_from_info(%{
lain's avatar
lain committed
1195
        source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
lain's avatar
lain committed
1196
1197
      }) do
    key =
Maksim's avatar
Maksim committed
1198
1199
      public_key_pem
      |> :public_key.pem_decode()
lain's avatar
lain committed
1200
1201
      |> hd()
      |> :public_key.pem_entry_decode()
1202

lain's avatar
lain committed
1203
    {:ok, key}
1204
1205
1206
  end

  # OStatus Magic Key
lain's avatar
lain committed
1207
  def public_key_from_info(%{magic_key: magic_key}) do
1208
1209
1210
    {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
  end

1211
  def get_public_key_for_ap_id(ap_id) do
1212
1213
    with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
         {:ok, public_key} <- public_key_from_info(user.info) do
1214
1215
1216
1217
1218
      {:ok, public_key}
    else
      _ -> :error
    end
  end
lain's avatar
lain committed
1219

1220
1221
1222
  defp blank?(""), do: nil
  defp blank?(n), do: n

lain's avatar
lain committed
1223
  def insert_or_update_user(data) do
lain's avatar
lain committed
1224
1225
1226
1227
    data =
      data
      |> Map.put(:name, blank?(data[:name]) || data[:nickname])

lain's avatar
lain committed
1228
    cs = User.remote_user_creation(data)
lain's avatar
lain committed
1229

lain's avatar
lain committed
1230
1231
    Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
  end
1232

1233
  def ap_enabled?(%User{local: true}), do: true
lain's avatar
lain committed
1234
  def ap_enabled?(%User{info: info}), do: info.ap_enabled
lain's avatar
lain committed
1235
  def ap_enabled?(_), do: false
lain's avatar
lain committed
1236

Maksim's avatar
Maksim committed
1237
1238
1239
1240
  @doc "Gets or fetch a user by uri or nickname."
  @spec get_or_fetch(String.t()) :: User.t()
  def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
  def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264

  # wait a period of time and return newest version of the User structs
  # this is because we have synchronous follow APIs and need to simulate them
  # with an async handshake
  def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
    with %User{} = a <- Repo.get(User, a.id),
         %User{} = b <- Repo.get(User, b.id) do
      {:ok, a, b}
    else
      _e ->
        :error
    end
  end

  def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
    with :ok <- :timer.sleep(timeout),
         %User{} = a <- Repo.get(User, a.id),
         %User{} = b <- Repo.get(User, b.id) do
      {:ok, a, b}
    else
      _e ->
        :error
    end
  end
Maxim Filippov's avatar
Maxim Filippov committed
1265

1266
  def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
lain's avatar
lain committed
1267
1268
  def parse_bio(nil, _user), do: ""
  def parse_bio(bio, _user) when bio == "", do: bio
1269
1270

  def parse_bio(bio, user) do
Maxim Filippov's avatar
Maxim Filippov committed
1271
1272
1273
1274
1275
1276
1277
    emoji =
      (user.info.source_data["tag"] || [])
      |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
      |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
        {String.trim(name, ":"), url}
      end)

1278
    # TODO: get profile URLs other than user.ap_id
1279
    profile_urls = [user.ap_id]
1280

Maksim's avatar
Maksim committed
1281
    bio
Haelwenn's avatar
Haelwenn committed
1282
    |> CommonUtils.format_input("text/plain",
1283
1284
      mentions_format: :full,
      rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
Haelwenn's avatar
Haelwenn committed
1285
    )
minibikini's avatar
minibikini committed
1286
    |> elem(0)
Maksim's avatar
Maksim committed
1287
    |> Formatter.emojify(emoji)
Maxim Filippov's avatar
Maxim Filippov committed
1288
  end
1289

1290
1291
1292
1293
1294
  def tag(user_identifiers, tags) when is_list(user_identifiers) do
    Repo.transaction(fn ->
      for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
    end)
  end
1295

Maksim's avatar
Maksim committed
1296
1297
1298
1299
  def tag(nickname, tags) when is_binary(nickname),
    do: tag(User.get_by_nickname(nickname), tags)

  def tag(%User{} = user, tags),
1300
    do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
Maksim's avatar
Maksim committed
1301

1302
1303
1304
1305
1306
  def untag(user_identifiers, tags) when is_list(user_identifiers) do
    Repo.transaction(fn ->
      for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
    end)
  end
1307

1308
1309
  def untag(nickname, tags) when is_binary(nickname),
    do: untag(User.get_by_nickname(nickname), tags)
1310

1311
1312
  def untag(%User{} = user, tags),
    do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1313

1314
1315
1316
1317
  defp update_tags(%User{} = user, new_tags) do
    {:ok, updated_user} =
      user
      |> change(%{tags: new_tags})
1318
      |> update_and_set_cache()
1319

1320
    updated_user
1321
  end
Ivan Tashkinov's avatar
Ivan Tashkinov committed
1322

Haelwenn's avatar
Haelwenn committed
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
  def bookmark(%User{} = user, status_id) do
    bookmarks = Enum.uniq(user.bookmarks ++ [status_id])
    update_bookmarks(user, bookmarks)
  end

  def unbookmark(%User{} = user, status_id) do
    bookmarks = Enum.uniq(user.bookmarks -- [status_id])
    update_bookmarks(user, bookmarks)
  end

  def update_bookmarks(%User{} = user, bookmarks) do
    user
    |> change(%{bookmarks: bookmarks})
    |> update_and_set_cache
  end

1339
1340
1341
1342
1343
  defp normalize_tags(tags) do
    [tags]
    |> List.flatten()
    |> Enum.map(&String.downcase(&1))
  end
href's avatar
href committed
1344

1345
  defp local_nickname_regex do
href's avatar
href committed
1346
1347
1348
1349
1350
1351
    if Pleroma.Config.get([:instance, :extended_nickname_format]) do
      @extended_local_nickname_regex
    else
      @strict_local_nickname_regex
    end
  end
lain's avatar
lain committed
1352

1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
  def local_nickname(nickname_or_mention) do
    nickname_or_mention
    |> full_nickname()
    |> String.split("@")
    |> hd()
  end

  def full_nickname(nickname_or_mention),
    do: String.trim_leading(nickname_or_mention, "@")

lain's avatar
lain committed
1363
1364
1365
1366
1367
1368
1369
1370
1371
  def error_user(ap_id) do
    %User{
      name: ap_id,
      ap_id: ap_id,
      info: %User.Info{},
      nickname: "erroruser@example.com",
      inserted_at: NaiveDateTime.utc_now()
    }
  end
minibikini's avatar
Reports    
minibikini committed
1372
1373
1374
1375
1376
1377
1378
1379
1380

  def all_superusers do
    from(
      u in User,
      where: u.local == true,
      where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
    )
    |> Repo.all()
  end
Maxim Filippov's avatar
Maxim Filippov committed
1381
1382
1383
1384
1385
1386
1387

  defp paginate(query, page, page_size) do
    from(u in query,
      limit: ^page_size,
      offset: ^((page - 1) * page_size)
    )
  end
1388
1389

  def showing_reblogs?(%User{} = user, %User{} = target) do
1390
    target.ap_id not in user.info.muted_reblogs
1391
  end
lain's avatar
lain committed
1392
end
For faster browsing, not all history is shown. View entire blame