Send a file to Telegram with Julia - julia

I'm writing a Telegram bot in Julia and I can't figure out how to send files correctly.
I've seen similar questions for other languages but it wasn't helpful.
Currently, I have such function:
function sendDoc(chat, fileName::String)
file = open(fileName)
url = string(base_url, "sendDocument")
mp = HTTP.Multipart(basename(fileName), file)
query = Dict("chat_id" => chat)
HTTP.post(url; query=query, files=Dict("document" => mp))
close(file)
end
I try it on a simple test.txt file with the content abc so the size of a file or it's name can't be a reason for any errors.
What I get from it (and also in case if I use file instead of mp) is an HTTP 400 error (I removed some parts for privacy purposes):
ERROR: LoadError: HTTP.ExceptionRequest.StatusError(400, "POST", "/bot/sendDocument?chat_id=", HTTP.Messages.Response(v"1.1.0", 400, Pair{SubString{String},SubString{String}}["Server" => "nginx/1.16.1", "Date" => "Thu, 12 Mar 2020 GMT", "Content-Type" => "application/json", "Content-Length" => "94", "Connection" => "keep-alive", "Strict-Transport-Security" => "max-age=31536000; includeSubDomains; preload", "Access-Control-Allow-Origin" => "*", "Access-Control-Expose-Headers" => "Content-Length,Content-Type,Date,Server,Connection"], UInt8[0x7b, 0x22, 0x6f, 0x6b, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73 … 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x7d], HTTP.Messages.Request("POST", "/bot/sendDocument?chat_id=", v"1.1.0", Pair{SubString{String},SubString{String}}["Host" => "api.telegram.org", "User-Agent" => "HTTP.jl/1.3.1", "Content-Length" => "0"], UInt8[], HTTP.Messages.Response(#= circular reference #-2 =#), 1, nothing)))
I also tried putting mp or file in body instead of files but then I get the following:
┌ Info: Error in #async writebody task. Server likely closed the connection unexpectedly. Only an issue if unable to read the response and this error gets re-thrown.
│ exception =
│ MethodError: no method matching write(::HTTP.Streams.Stream{HTTP.Messages.Response,HTTP.ConnectionPool.Transaction{MbedTLS.SSLContext}}, ::Pair{String,IOStream})
│ Closest candidates are:
│ write(::IO, ::Any) at io.jl:582
│ write(::IO, ::Any, ::Any...) at io.jl:584
│ write(::IO, ::Complex) at complex.jl:217
Including the file in query is also something I could try but then it would be in the URL and I'd be very limited in file size.
The 400 error looks the same as if it would be missing a field, so I assume that Telegram can't see the Dict("document" => mp), but at the same time error includes UInt8[0x7b, … 0x7d] which looks like the contents of the file. How do I make it right?

Turns out, it's not HTTP.Multipart that I needed, it's HTTP.Form.
The following function works:
function sendDoc(chat, fileName::String)
url = string(base_url, "sendDocument")
file = open(fileName)
query = Dict("chat_id" => chat)
HTTP.post(url, query=query; body=HTTP.Form(Dict("document" => file)))
close(file)
end

If you want to, you may use Telegram.jl package, which in particular includes support for document and images sending.
You can do it like this
using Telegram, Telegram.API
tg = TelegramClient(bot_token; chat_id = chat)
open("picture.png", "r") do io
sendPhoto(photo = io)
end
Here bot_token and chat are bot token and chat id.

Let's say you have a photo in your directory "Foto.jpg". To send a photo, you would need this:
fileName = "Foto.jpg"
url_photo = string("https://api.telegram.org/bot",bot_token,"/sendPhoto")
file = open(fileName)
query = Dict("chat_id" => bot_manu)
HTTP.post(url_photo, query=query; body=HTTP.Form(Dict("photo" => file)))
close(file)
Where 'bot_manu' should be your chat id, 'bot_token' is the bot id token. Hope this helps for those who wanted to send a photo. To get 'bot_manu' and 'bot_token' I used BotFather.

Related

UTL_HTTP and french accents

I try to call a web service using the package UTL_HTTP, it works but I have an issue with french accents( 'é' and 'è') , the UTF-8 is not working .
CONTENT := '{
"metier": {
"REF_CONTRAT": "'||ref_contrat||'",
"ID_HISTO": "'||id_histo||'",
"ID_OBJ_DECLENCHEUR": "'||ID_OBJ_DECLENCHEUR||'",
"TYPE_OBJ_DECLENCHEUR": "'||type_obj_declencheur||'",
"ID_SCENARIO_INSTANCIE": "'||ID_SCENARIO_INSTANCIE||'",
"ID_SCENARIO": "'||id_scenario||'",
"ID_SMS": "'||id_sms||'",
"REPONSE_RECUE": "'||reponse_recue||'",
"ACTION": "'||ACTION||'",
"TYPE_ACTION": "'||TYPE_ACTION||'"
}
}';
-- V_URL:=UTL_URL.ESCAPE(V_URL,FALSE,'UTF-8');
UTL_HTTP.SET_BODY_CHARSET('UTF-8');
REQ := UTL_HTTP.BEGIN_REQUEST(V_URL, V_METHODE,' HTTP/1.1');
UTL_HTTP.SET_HEADER(REQ, 'user-agent', 'mozilla/4.0');
-- UTL_HTTP.SET_HEADER(REQ, 'content-type', V_CONTENT_TYPE);
UTL_HTTP.SET_HEADER(REQ, 'content-type','application/json; charset="UTF-8"');
utl_http.set_header(req, 'Content-Length', lengthB(content));
UTL_HTTP.SET_HEADER(REQ,'Authorization',V_AUTHORIZATION);
UTL_HTTP.WRITE_TEXT(REQ, CONTENT);
/* UTL_HTTP.WRITE_RAW (R => REQ,
data => UTL_RAW.CAST_TO_RAW(CONTENT)); */
I have error 500 as a response.
any idea please ? I tried all what I found on the internet but it's not working
The problem was that length doesn't give the right length when the question contains combined characters ; é was counted as 1 byte instead of 2 so it worked when I converted the request to AL32UTF8 : length(Convert(CONTENT,'AL32UTF8'))

swi prolog 8.0.2 : gziped http

I tried to make work a piece of code that opens an http connection.
Nevertheless, web page may transfered as plain text or gziped.
As a result, the code with pragmatism tries to open as plain text and if it fails and receives an exception, tries as if it is gzip encoded.
URL is the sole variable to ground.
Try with URL = 'http://releases.llvm.org/6.0.0/tools/clang/docs/ClangCommandLineReference.html' for instance.
user::catch(
(
user::http_open(URL, DataStream, []),
user::load_html(stream(DataStream), Terms, []),
user::close(DataStream)
),
_
,
(
user::open_any(URL, read, GZipDataStream, CloseIt, [encoding(gzip), string(atom)]),
/*user::http:encoding_filter(gzip, DataStream, GZipDataStream),*/
user::load_html(stream(GZipDataStream), Terms, []),
user::close_any(CloseIt)
)
)
Infortunately, the recovery part of catch doesn't work.
Any suggestion, please ?
The user:: prefixes in the goals suggests that the code you posted is a fragment of Logtalk. If so, it's misusing Logtalk source code and creating a dependency on the SWI-Prolog autoloading mechanism. The code can be rewritten for clarity and resilience. Doing that and fixing the bug in it (library(zlib) must be loaded to make avaialble the http:encoding_filter/3 filter) results in the following solution:
:- use_module(library(http/http_open), []).
:- use_module(library(sgml), []).
:- use_module(library(iostream), []).
:- use_module(library(zlib), []).
:- object(html).
:- public(get_url/2).
% override ambiguous meta-predicate template
:- meta_predicate(sgml:load_html(*,*,*)).
get_url(URL, Terms) :-
catch(
setup_call_cleanup(
http:http_open(URL, DataStream, []),
sgml:load_html(stream(DataStream), Terms, []),
close(DataStream)
),
_,
setup_call_cleanup(
iostream:open_any(URL, read, DataStream, CloseIt, [string(atom)]),
sgml:load_html(stream(DataStream), Terms, []),
iostream:close_any(CloseIt)
)
).
:- end_object.
The setup_call_cleanup/3 calls ensure that the opened streams are closed in case of error.
Assuming the object above is saved in a html.lgt file, the following sample call shows it working for the URL you posted:
?- {html}.
...
% (0 warnings)
true.
?- html::get_url('http://releases.llvm.org/6.0.0/tools/clang/docs/ClangCommandLineReference.html', Terms).
Terms = [element(html, [xmlns='http://www.w3.org/1999/xhtml'], [element(head, [], [element(meta, ['http-equiv'='Content-Type', content='text/html; charset=utf-8'], []), element(title, [], ['Clang command line argument reference — Clang 6 documentation']), element(link, [... = ...|...], []), element(link, [...|...], []), element(..., ..., ...)|...]), element(body, [role=document], [' ', element(div, [... = ...|...], [element(..., ..., ...)|...]), '\n ', element(..., ..., ...)|...])])].

Ejabberd: error in simple module to handle offline messages

I have an Ejabberd 17.01 installation where I need to push a notification in case a recipient is offline. This seems the be a common task and solutions using a customized Ejabberd module can be found everywhere. However, I just don't get it running. First, here's me script:
-module(mod_offline_push).
-behaviour(gen_mod).
-export([start/2, stop/1]).
-export([push_message/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
start(Host, _Opts) ->
?INFO_MSG("mod_offline_push loading", []),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_message, 10),
ok.
stop(Host) ->
?INFO_MSG("mod_offline_push stopping", []),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_message, 10),
ok.
push_message(From, To, Packet) ->
?INFO_MSG("mod_offline_push -> push_message", [To]),
Type = fxml:get_tag_attr_s(<<"type">>, Packet), % Supposedly since 16.04
%Type = xml:get_tag_attr_s(<<"type">>, Packet), % Supposedly since 13.XX
%Type = xml:get_tag_attr_s("type", Packet),
%Type = xml:get_tag_attr_s(list_to_binary("type"), Packet),
?INFO_MSG("mod_offline_push -> push_message", []),
ok.
The problem is the line Type = ... line in method push_message; without that line the last info message is logged (so the hook definitely works). When browsing online, I can find all kinds of function calls to extract elements from Packet. As far as I understand it changed over time with new releases. But it's not good, all variants lead in some kind of error. The current way returns:
2017-01-25 20:38:08.701 [error] <0.21678.0>#ejabberd_hooks:run1:332 {function_clause,[{fxml,get_tag_attr_s,[<<"type">>,{message,<<>>,normal,<<>>,{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>},[],[{text,<<>>,<<"sfsdfsdf">>}],undefined,[],#{}}],[{file,"src/fxml.erl"},{line,169}]},{mod_offline_push,push_message,3,[{file,"mod_offline_push.erl"},{line,33}]},{ejabberd_hooks,safe_apply,3,[{file,"src/ejabberd_hooks.erl"},{line,382}]},{ejabberd_hooks,run1,3,[{file,"src/ejabberd_hooks.erl"},{line,329}]},{ejabberd_sm,route,3,[{file,"src/ejabberd_sm.erl"},{line,126}]},{ejabberd_local,route,3,[{file,"src/ejabberd_local.erl"},{line,110}]},{ejabberd_router,route,3,[{file,"src/ejabberd_router.erl"},{line,87}]},{ejabberd_c2s,check_privacy_route,5,[{file,"src/ejabberd_c2s.erl"},{line,1886}]}]}
running hook: {offline_message_hook,[{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>},{message,<<>>,normal,<<>>,{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>},[],[{text,<<>>,<<"sfsdfsdf">>}],undefined,[],#{}}]}
I'm new Ejabberd and Erlang, so I cannot really interpret the error, but the Line 33 as mentioned in {mod_offline_push,push_message,3,[{file,"mod_offline_push.erl"}, {line,33}]} is definitely the line calling get_tag_attr_s.
UPDATE 2017/01/27: Since this cost me a lot of headache -- and I'm still not perfectly happy -- I post here my current working module in the hopes it might help others. My setup is Ejabberd 17.01 running on Ubuntu 16.04. Most stuff I tried and failed with seem to for older versions of Ejabberd:
-module(mod_fcm_fork).
-behaviour(gen_mod).
%% public methods for this module
-export([start/2, stop/1]).
-export([push_notification/3]).
%% included for writing to ejabberd log file
-include("ejabberd.hrl").
-include("logger.hrl").
-include("xmpp_codec.hrl").
%% Copied this record definition from jlib.hrl
%% Including "xmpp_codec.hrl" and "jlib.hrl" resulted in errors ("XYZ already defined")
-record(jid, {user = <<"">> :: binary(),
server = <<"">> :: binary(),
resource = <<"">> :: binary(),
luser = <<"">> :: binary(),
lserver = <<"">> :: binary(),
lresource = <<"">> :: binary()}).
start(Host, _Opts) ->
?INFO_MSG("mod_fcm_fork loading", []),
% Providing the most basic API to the clients and servers that are part of the Inets application
inets:start(),
% Add hook to handle message to user who are offline
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_notification, 10),
ok.
stop(Host) ->
?INFO_MSG("mod_fcm_fork stopping", []),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_notification, 10),
ok.
push_notification(From, To, Packet) ->
% Generate JID of sender and receiver
FromJid = lists:concat([binary_to_list(From#jid.user), "#", binary_to_list(From#jid.server), "/", binary_to_list(From#jid.resource)]),
ToJid = lists:concat([binary_to_list(To#jid.user), "#", binary_to_list(To#jid.server), "/", binary_to_list(To#jid.resource)]),
% Get message body
MessageBody = Packet#message.body,
% Check of MessageBody is not empty
case MessageBody/=[] of
true ->
% Get first element (no idea when this list can have more elements)
[First | _ ] = MessageBody,
% Get message data and convert to string
MessageBodyText = binary_to_list(First#text.data),
send_post_request(FromJid, ToJid, MessageBodyText);
false ->
?INFO_MSG("mod_fcm_fork -> push_notification: MessageBody is empty",[])
end,
ok.
send_post_request(FromJid, ToJid, MessageBodyText) ->
%?INFO_MSG("mod_fcm_fork -> send_post_request -> MessageBodyText = ~p", [Demo]),
Method = post,
PostURL = gen_mod:get_module_opt(global, ?MODULE, post_url,fun(X) -> X end, all),
% Add data as query string. Not nice, query body would be preferable
% Problem: message body itself can be in a JSON string, and I couldn't figure out the correct encoding.
URL = lists:concat([binary_to_list(PostURL), "?", "fromjid=", FromJid,"&tojid=", ToJid,"&body=", edoc_lib:escape_uri(MessageBodyText)]),
Header = [],
ContentType = "application/json",
Body = [],
?INFO_MSG("mod_fcm_fork -> send_post_request -> URL = ~p", [URL]),
% ADD SSL CONFIG BELOW!
%HTTPOptions = [{ssl,[{versions, ['tlsv1.2']}]}],
HTTPOptions = [],
Options = [],
httpc:request(Method, {URL, Header, ContentType, Body}, HTTPOptions, Options),
ok.
Actually it fails with second arg Packet you pass to fxml:get_tag_attr_s in push_message function
{message,<<>>,normal,<<>>,
{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,
<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},
{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,
<<"xxx.xxx.xxx.xxx">>,<<>>},
[],
[{text,<<>>,<<"sfsdfsdf">>}],
undefined,[],#{}}
because it is not xmlel
Looks like it is record "message" defined in tools/xmpp_codec.hrl
with <<>> id and type 'normal'
xmpp_codec.hrl
-record(message, {id :: binary(),
type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal',
lang :: binary(),
from :: any(),
to :: any(),
subject = [] :: [#text{}],
body = [] :: [#text{}],
thread :: binary(),
error :: #error{},
sub_els = [] :: [any()]}).
Include this file and use just
Type = Packet#message.type
or, if you expect binary value
Type = erlang:atom_to_binary(Packet#message.type, utf8)
The newest way to do that seems to be with xmpp:get_type/1:
Type = xmpp:get_type(Packet),
It returns an atom, in this case normal.

Asterisk Cannot record call after fetching it from parking lot

I'm using asterisk/freepbx. Asterisk is not recording calls that are fetched from park. Here is my call flow.
A calls to Asterisk Server(AS)
Call is picked up by extension B
B does an attended transfer by dialling *2200 (200 is my default parking lot)
C dials 1 to fetch the parked call
C dials *1 to record the call.
Recording is not done.
I found a fix and it works for me. In the asterisk log i found that asterisk tries to record to an invalid file with no filename, just an extension(.wav). It executed 2 files - /var/lib/asterisk/agi-bin/parkfetch.agi and /var/lib/asterisk/bin/one_touch_record.php.
one_touch_record.php generates filename from data read from channel, like year, date, mixmonitor folder etc, but as there was no valid filename in the log, these should be null here.
$mixMonDir = getVariable($channel, "MIXMON_DIR");
$year = getVariable($channel, "YEAR");
$month = getVariable($channel, "MONTH");
$day = getVariable($channel, "DAY");
$mixMonFormat = getVariable($channel, "MIXMON_FORMAT");
$mixMonPost = getVariable($channel, "MIXMON_POST");
$astman->mixmonitor($channel, "{$mixMonDir}{$year}/{$month}/{$day}/{$callFileName}.{$mixMonFormat}", "a", $mixMonPost, rand());
So i inspected the parkfetch.agi were i found that these channel vars are copied only if REC_STATUS is "RECORDING" and in this case REC_STATUS is "INITIALIZED". So i added an OR clause ie i changed if ($rec_status == "RECORDING") to if ($rec_status == "RECORDING" || $rec_status=="INITIALIZED")
if ($channel) {
$rec_status = get_var("IMPORT($channel,REC_STATUS)");
$agi->set_variable('REC_STATUS', $rec_status);
if ($rec_status == "RECORDING" || $rec_status=="INITIALIZED") {
foreach (array('MIXMON_DIR', 'YEAR', 'MONTH', 'DAY', 'CALLFILENAME', 'MIXMON_FORMAT', 'MIXMON_POST', 'MON_FMT') as $v) {
$agi->set_variable($v, get_var("IMPORT($channel,$v)"));
}
}
}
And it worked. Now when I pressed *1 after fetching call from park, it is getting recorded.
If someone found a better solution, please leave it as a comment in my blog.
http://sachindotg.blogspot.in/2014/02/asterisk-cannot-record-call-after.html

Puppet syncing dir

Still does not work
In/etc/puppet/manifests/site.pp
file { "/home/render/installation/":
ensure => "directory",
owner => "render",
group => "render",
recurse => "true",
mode => "0750",
source => "puppet:///files/installation/",
}
Dir still is empty on client
ls /etc/puppet/files/installation/
1 2 3 4 5
On puppet client in log
Mar 21 12:28:12 lw-003 puppet-agent[28098]: (/File[/home/render/installation/]) Failed to generate additional resources using 'eval_generate: Error 400 on SERVER: Not authorized to call search on /file_metadata/files/installation with {:checksum_type=>"md5", :recurse=>true, :links=>"manage"}
Mar 21 12:28:12 lw-003 puppet-agent[28098]: (/File[/home/render/installation/]) Could not evaluate: Error 400 on SERVER: Not authorized to call find on /file_metadata/files/installation Could not retrieve file metadata for puppet:///files/installation: Error 400 on SERVER: Not authorized to call find on /file_metadata/files/installation
Mar 21 12:28:12 lw-003 puppet-agent[28098]: Finished catalog run in 0.28 seconds
I had the same issue and found this question on Google. I had to change the path to the files for the manifests (modulename/manifests/init.pp):
"puppet:///files/installation/",
to:
"puppet:///modules/files/installation/",
The notation without /modules/ was deprecated in 2.7 and no longer supported in newer versions:
DEPRECATION NOTICE: Files found in modules without specifying 'modules' in file path will be deprecated in the next major release. Please fix module 'modulename' when no 0.24.x clients are present
try this example,
file {
"/scratch/usern/testmod" :
ensure => directory,
source => "puppet:///files/testmod",
recurse => true,
owner => "usern",
group => "groupn",
mode => "0775",
backup => false,
}
You have to specify 'files' and if you are doing a recursive copy, specify 'recurse => true'. That might be the solution to your problem.
the 'fileserver.conf' should look something like the following :
]# cat /etc/puppet/fileserver.conf
[files]
path /etc/puppet/files
allow *

Resources