How to load a module inside a custom module in Ejabberd? - module
I'm creating a custom module which uses some exported functions from mod_shared_roster module. Here is the code.
-module(mod_shared_roster_extra).
-author('https://github.com/eric-mendoza').
-behaviour(gen_mod).
-include("logger.hrl").
-include("translate.hrl").
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2, mod_doc/0]).
% Commands API
-export([
% Shared roster
srg_display_group_add/4, srg_display_group_del/4, srg_set_opts/4
]).
-include("ejabberd_commands.hrl").
-include("mod_roster.hrl").
-include("mod_privacy.hrl").
-include("ejabberd_sm.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("mod_shared_roster.hrl").
%%%
%%% gen_mod
%%%
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[{mod_shared_roster, hard}].
%%%
%%% Register commands
%%%
get_commands_spec() ->
[
#ejabberd_commands{name = srg_display_group_add, tags = [shared_roster_group],
desc = "Add group id to the Shared Roster Group display list",
module = ?MODULE, function = srg_display_group_add,
args = [{displaygroup, binary}, {displaygrouphost, binary}, {group, binary}, {grouphost, binary}],
args_example = [<<"group1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
args_desc = ["Group to be added in display list", "Group server name", "Group to modify display list identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_display_group_del, tags = [shared_roster_group],
desc = "Delete group id from the Shared Roster Group",
module = ?MODULE, function = srg_display_group_del,
args = [{displaygroup, binary}, {displaygrouphost, binary}, {group, binary}, {grouphost, binary}],
args_example = [<<"group1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
args_desc = ["Group to be removed from display list", "Group server name", "Group to modify display list identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_set_opts, tags = [shared_roster_group],
desc = "Update Shared Roster Group options (name and description)",
module = ?MODULE, function = srg_set_opts,
args = [{name, binary}, {description, binary}, {group, binary}, {grouphost, binary}],
args_example = [<<"Group Test Name">>, <<"Group used for testing.">>, <<"group3">>, <<"myserver.com">>],
args_desc = ["Shared roster group name", "Shared Roster Group description", "Group identifier", "Group server name"],
result = {res, rescode}}
].
%%%
%%% Shared Roster Groups
%%%
to_list([]) -> [];
to_list([H|T]) -> [to_list(H)|to_list(T)];
to_list(E) when is_atom(E) -> atom_to_list(E);
to_list(E) -> binary_to_list(E).
srg_display_group_add(NewGroup, NewGroupHost, Group, GroupHost) ->
?DEBUG("Adding group to display list.", []),
Opts = mod_shared_roster:get_group_opts(Group, GroupHost),
mod_shared_roster:set_group_opts(GroupHost, Group, Opts),
ok.
srg_display_group_del(DeleteGroup, DeleteGroupHost, Group, GroupHost) ->
?DEBUG("Removing group from display list.", []),
Opts = mod_shared_roster:get_group_opts(Group, GroupHost),
%% mod_shared_roster:set_group_opts(GroupHost, Group, Opts),
ok.
srg_set_opts(Label1, Description1, Group, GroupHost) ->
?DEBUG("Setting group options -> Label: ~p, Description: ~p~n", [Label1, Description1]),
Opts = mod_shared_roster:get_group_opts(Group, GroupHost),
Label = if Label1 == <<"">> -> [];
true -> [{label, Label1}]
end,
Description = if Description1 == <<"">> -> [];
true -> [{description, Description1}]
end,
Displayed1 = get_opt(Opts, displayed_groups, []),
Displayed = if Displayed1 == [] -> [];
true -> [{displayed_groups, Displayed1}]
end,
?DEBUG("Options: ~p~n", [Label ++ Description ++ Displayed]),
mod_shared_roster:set_group_opts(GroupHost, Group, Label ++ Description ++ Displayed),
ok.
mod_options(_) -> [].
get_opt(Opts, Opt, Default) ->
case lists:keysearch(Opt, 1, Opts) of
{value, {_, Val}} -> Val;
false -> Default
end.
mod_doc() ->
#{desc =>
?T("This module provides additional administrative commands for shared rosters.")}.
Module installs correctly, but when I call the method srg_set_opts which uses mod_shared_roster:get_group_opts I get the following error:
error: {module_not_loaded, mod_shared_roster, <<"srg_group10">>}
I've seen other modules as guide, and the only difference I've noticed is those modules dependencies are contained in the same directory. But, how could I achieve that with a custom module? I'm not experienced with Erlang, so it could be just a simple import.
I'm using ejabberd 20.04 installed via docker-ejabberd, and used Ejabberd module docs for Docker to install the module.
Opts = mod_shared_roster:get_group_opts(Group, GroupHost),
I think the arguments of that function are the Host and then the Group, but you provide them in the reverse order.
Related
Menu variable not in scope Haskell
I am trying to create a menu which read the data from a textfile. But I get three errors that variable not in scope despite having at the start of the IO(). Am I not reading the txt.file properly and is the way I creating the menu wrong? errors Variable not in scope: spaDatabase :: [Spa] --line of error for spaDatabase let updatedDB = (addSpa rid br ar (read st) spaDatabase) Variable not in scope: spaDB Variable not in scope: updatedDB :: [Spa] --line of error for updatedDB 2 -> putStrLn (spaListStr updatedDB) >> menu spaDB My code main :: IO() main = do contents <- readFile "spa.txt" let spaDatabase = (read contents :: [Spa]) menu spaDatabase putStrLn "" where menu spaDatabase = do putStrLn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" putStrLn "\nPlease select an option:" putStrLn "1: Add a new spa to the database " putStrLn "2: Show all spa " putStr "\nSelected option: " putStrLn "" option <- getLine putStrLn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" output :: Int -> IO () output option = do case option of 1 -> do putStrLn "Enter Spa ID: " rid <- getLine putStrLn "Enter Spa Brand Name: " br <- getLine putStrLn "Enter Spa Area: " ar <- getLine putStrLn "Enter Spa Stars: " st <- getLine let updatedDB = (addSpa rid br ar (read st) spaDatabase) putStrLn (spaListStr updatedDB) writeFile "spa.txt" (spaListStr updatedDB) 2 -> putStrLn (spaListStr updatedDB) >> menu spaDB
where is part of a definition, not of expression. It belongs to the main's definition so its scope goes above do's. Your originally posted code main = do { ..... let c = ..... ..... } where a = .... b = ....c.... ..... is equivalent to main = let { -- where's translation a = .... b = ....c.... ..... } in do { ..... let c = ..... ..... } You're using c in b = ....c.... before it is introduced with let c = ....., i.e. outside of its scope region. That's an error, "variable not in scope". update. let in do blocks is just a shortcut: do { ..... let c = ..... ..... } is the same as do { ..... let c = ..... in do { ..... } } The working code could be structured, as also #chi proposed in the comments, as main = do { ..... let c = ..... ..... let { a = .... b = ....c.... } ..... } That way a and b are in c's scope, so may use c as part of their definitions.
How do you iterate a List (Maybe a)
I have the following graphQL result: [Just { details = Just "Engine failure at 33 seconds and loss of vehicle", launch_year = Just "2006", links = Just { article_link = Just "https://www.space.com/2196-spacex-inaugural-falcon-1-rocket-lost-launch.html" }, mission_name = Just "FalconSat" }] Based on the following types: type alias Launch = { mission_name : Maybe String , details : Maybe String , launch_year : Maybe String , links : Maybe LaunchLinks } type alias Launches = Maybe (List (Maybe Launch)) type alias LaunchLinks = { article_link : Maybe String } I want to List.map through and display the results in unordered list. I started with this: renderLaunch : Launches -> Html Msg renderLaunch launches = div [] <| case launches of Nothing -> [ text "Nothing here" ] Just launch -> launch |> List.map (\x -> x) |> ul [] But I keep getting this error: This function cannot handle the argument sent through the (|>) pipe: 141| launch 142| |> List.map (\x -> x) 143| |> ul [] ^^^^^ The argument is: List (Maybe Launch) But (|>) is piping it a function that expects: List (Html msg)
The problem is that the Just launch case needs to result in a List (Html msg) but the code results in a different type being returned. When you are using List.map (\x -> x), it is essentially a no-op. You are iterating over a List (Maybe Launch) and returning the same thing. I'd recommend creating another function that takes a Maybe Launch value and use that as your mapping function. For example: displayLaunch : Maybe Launch -> Html Msg displayLaunch launch = case launch of Nothing -> text "No launch" Just l -> text (Debug.toString l) Now you can plug that into your mapping function: Just launch -> launch |> List.map displayLaunch |> ul [] But, whoops! Now you get a new error indicating: The 2nd branch is: Html Msg But all the previous branches result in: List (Html msg) The problem here is that we are now returning a ul from the Just launch branch and we need to return a list of html. You can use List.singleton to create a list with just one item: Just launch -> launch |> List.map displayLaunch |> ul [] |> List.singleton
Can't decode session from elm port
Trying to get elm ports working to maintain the session. In index.html, the script includes the following listener: window.addEventListener("load", function(event) { app.ports.onSessionChange.send(localStorage.session); }, false); localStorage.session looks like this (and it stays there until I've logged out): {"email":"user#fake.com","token":"eyJhbG...","user_id":1,"handle":"me"} The definition in Ports.elm is: port onSessionChange : (Value -> msg) -> Sub msg This port is connected to Main.elm here (let me know if I've forgotten to include some of the definitions below): subscriptions : Model -> Sub Msg subscriptions model = Ports.onSessionChange sessionChange sessionChange : Json.Decode.Value -> Msg sessionChange value = let result = Json.Decode.decodeValue sessionDecoder value in case result of Ok sess -> SetSession (Just sess) Err err -> SetSession Nothing ... type alias Session = { email : String , token : String , user_id : Int , handle : String } ... import Json.Decode as Decode exposing (..) import Json.Decode.Pipeline as Pipeline exposing (decode, required) sessionDecoder : Decode.Decoder Session sessionDecoder = Pipeline.decode Session |> Pipeline.required "email" Decode.string |> Pipeline.required "token" Decode.string |> Pipeline.required "user_id" Decode.int |> Pipeline.required "handle" Decode.string ... type Msg = NoOp | SetSession (Maybe Session) ... update msg model = case msg of SetSession session -> case Debug.log "session = " session of Just sess -> ({ model | session = sess } , Cmd.none) Nothing -> (model, Cmd.none) Debug.log "session" displays Nothing in the console when the page loads, so JS is talking to elm, but the decoder seems to be failing. Any ideas?
I've plugged your code into a minimal working example and everything works fine. You might want to log the value of localStorage.session from inside the javascript portion to make sure it's a valid JSON value.
How to get query parameters in Elm?
In my Elm program, I'd like to initialize my model based on the query string. For example, if the query string is ?w=3&h=5 I'd like to have: initialModel = { width = 3 , height = 5 } Is that possible to achieve this in Elm, or the only way to do this is to get the query parameters in Javascript and pass them via a port?
Elm 0.19 For elm 0.19 the below concept is the same. Both of these packages still exist but have been moved and relabeled as the official elm/url and elm/browser libraries. Elm 0.18 This example uses evancz/url-parser and elm-lang/navigation. There are a few kinks that aren't straightforward in the documentation, but I've explained them briefly below. The example should speak for itself. module Main exposing (..) import Html as H exposing (..) import Navigation exposing (Location) import UrlParser as UP exposing ((</>), (<?>), top, parsePath, oneOf, s, stringParam, Parser) import Maybe.Extra as MaybeExtra exposing (unwrap) type Route = UrlRoute (Maybe String) (Maybe String) | NotFoundRoute type Msg = UrlParser Navigation.Location type alias Model = { location : Route , w : String , h : String } type alias SearchParams = { w : Maybe String, h : Maybe String } main = Navigation.program UrlParser { init = init , view = view , update = update , subscriptions = (\_ -> Sub.none) } init : Location -> ( Model, Cmd Msg ) init location = let currentPath = parseLocation location in ( initialModel currentPath , Cmd.none ) parseLocation : Location -> Route parseLocation location = case (parsePath matchers location) of Just route -> route Nothing -> NotFoundRoute matchers : Parser (Route -> a) a matchers = UP.map UrlRoute (UP.s "index" <?> UP.stringParam "w" <?> UP.stringParam "h") initialModel : Route -> Model initialModel route = { location = route , w = MaybeExtra.unwrap "" (\x -> Maybe.withDefault "" x.w) (parseParams route) , h = MaybeExtra.unwrap "" (\x -> Maybe.withDefault "" x.h) (parseParams route) } parseParams : Route -> Maybe SearchParams parseParams route = case route of UrlRoute w h -> Just { w = w, h = h } NotFoundRoute -> Nothing update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of UrlParser location -> ( model , Cmd.none ) view : Model -> Html msg view model = div [] [ h1 [] [ text "URL Info" ] , div [] [ text ("W is: " ++ model.w) ] , div [] [ text ("H is: " ++ model.h) ] ] The "trick" is to create another type alias to place your query params inside of. In the above example I've created the type SearchParams. After creating this type we just use an initialModel that takes in the currentPath. From there, our model can extract the query params with Maybe.withDefault (it needs to be a Maybe type because the params may not be there). Once we have our data in the model we just print it out in the view. Hope this helps!
There is no built-in core library way to access the URL. You can use ports and the community library jessitron/elm-param-parsing. If you also want to set the URL, you can again use ports, or you can use the History API, for which there are bindings in TheSeamau5/elm-history.
Unfortunately jessitron/elm-param-parsing doesn't work with Elm 0.18. Use elm-lang/navigation package: http://package.elm-lang.org/packages/elm-lang/navigation/latest/Navigation https://github.com/elm-lang/navigation/tree/2.1.0 especially this function: program : (Location -> msg) -> { init : Location -> (model, Cmd msg), update : msg -> model -> (model, Cmd msg), view : model -> Html msg, subscriptions : model -> Sub msg } -> Program Never model msg In the second parameter you can see "init : Location -> (model, Cmd msg)". This should handle reading of initial URL. To complement that, first parameter is a function which gets called every time URL changes. (I am aware it's an old question, but this link popped out when I was looking for the solution to the same problem and accepted answer didn't help)
How to pass projects in order in FAKE
I need to build vb6 projects in order. How should I pass them to Vb6Make? let projs = [ "a.vbp" "b.vbp" "c.vbp" ] Target "VB6" (fun _ -> !! projs // <- ? |> Vb6Make )
!! and ++ can be used for single files files as well. And FullName is important for directories. let vb6dir = FullName "./bin" let tempdir = FullName "./temp" let projs = !! "src\a\a.vbp" ++ "src\b\b.vbp" ++ "src\c\c.vbp" Target "BuildVb6" (fun _ -> projs |> Fake.Vb6Helper.Vb6Make(fun c -> { c with Logdir = tempdir Outdir = vb6dir }) )