Elixir, using function from another module - function

I am extremely new to the programming and to the elixir. So I am very exited to learn as much as I can. But I've got a problem. I looking the way how to use my functions in another module. I am building the web-server which stores the key-value maps in the memory. To keep the maps temporary I've decided to use Agent. Here is the part of my code:
defmodule Storage do
use Agent
def start_link do
Agent.start_link(fn -> %{} end, name: :tmp_storage)
end
def set(key, value) do
Agent.update(:tmp_storage, fn map -> Map.put_new(map, key, value) end)
end
def get(key) do
Agent.get(:tmp_storage, fn map -> Map.get(map, key) end)
end
end
So I'm trying to put this functions to the routes of the web server:
defmodule Storage_router do
use Plug.Router
use Plug.Debugger
require Logger
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
post "/storage/set" do
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "getting the value")
else
_ ->
send_resp(conn, 404, "nothing")
end
end
end
And I receive:
warning: variable "key" does not exist and is being expanded to "key()", please use parentheses to remove the ambiguity or change the variable name
lib/storage_route.ex:12
warning: variable "value" does not exist and is being expanded to "value()", please use parentheses to remove the ambiguity or change the variable name
lib/storage_route.ex:12
looking for any suggestions\help

I am extremly new to the programming and to the elixir.
I do not think it is wise to begin learning programming with elixir. I would start with python or ruby, and then after a year or two then I would try elixir.
The first thing you need to learn is how to post code. Search google for how to post code on stackoverflow. Then, you have to get your indenting all lined up. Are you using a computer programming text editor? If not, then you have to get one. There are many free ones. I use vim, which comes installed on Unix like computers. You can learn how to use vim by typing vimtutor in a terminal window.
Next, you have a syntax error in your code:
Agent.start_link(fn -> %{} end, name: :tmp_storage
end)
That should be:
Agent.start_link(fn -> %{} end, name: :tmp_storage)
The warning you got is because your code tries to do the equivalent of:
def show do
IO.puts x
end
Elixir and anyone else reading that code would ask, "What the heck is x?" The variable x is never assigned a value anywhere, and therefore the variable x does not exist, and you cannot output something that is non-existent. You do the same thing here:
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "getting the value")
else
_->
send_resp(conn, 404, "nothing")
end
You call the function:
Storage.set(key, value)
but the variables key and value were never assigned a value, and elixir (and anyone else reading that code) wonders, "What the heck are key and value?"
This is the way functions work:
b.ex:
defmodule MyFuncs do
def show(x, y) do
IO.puts x
IO.puts y
end
end
defmodule MyWeb do
def go do
height = 10
width = 20
MyFuncs.show(height, width)
end
end
In iex:
~/elixir_programs$ iex b.ex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> MyWeb.go
10
20
:ok
iex(2)>
So, in your code you need to write something like this:
post "/storage/set" do
key = "hello"
value = 10
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "Server saved the key and value.")
else
_->
send_resp(conn, 404, "nothing")
end
end
However, that will store the same key/value for every post request. Presumably, you want to store whatever is sent in the body of the post request. Do you know the difference between a get request and a post request? A get request tacks data onto the end of the url, while a post request sends the data in the "body of the request", so there are different procedures for extracting the data depending on the type of the request.
What tutorial are you reading? This tutorial: https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/, shows you how to extract the data from the body of a post request. The data in the body of a post request is just a string. If the string is in JSON format, then you can convert the string into an elixir map using Poison.decode!(), which will allow you to easily extract the values associated with the keys that you are interested in. For example:
post "/storage/set" do
{:ok, body_string, conn} = read_body(conn)
body_map = Poison.decode!(body_string)
IO.inspect(body_map) #This outputs to terminal window where server is running
message = get_in(body_map, ["message"])
send_resp(
conn,
201,
"Server received: #{message}\n"
)
end
Then you can use the following curl command in another terminal window to send a post request to that route:
$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"message": "hello world" }'
(-v => verbose output, -H => request header, -d => data)
Now, based on what I said was wrong with your code above, you should be wondering about this line:
{:ok, body_string, conn} = read_body(conn)
That line calls:
read_body(conn)
but the variable conn is not assigned a value anywhere. However, Plug invisibly creates the conn variable and assigns a value to it.
Here is a complete example using Agent to store post request data (following the tutorial I linked above):
simple_server
config/
lib/
simple_server/
application.ex
router.ex
storage.ex
test/
An elixir convention is to have a directory in the lib/ directory with the same name as your project, in this case that would be simple_server, then you give the modules you define names that reflect the directory structure. So, in router.ex you would define a module named SimpleServer.Router and in storage.ex you would define a module named SimpleServer.Storage. However, the . in a module name means nothing special to elixir, so you will not get an error if you decide to name your module F.R.O.G.S in the file lib/rocks.ex--and your code will work just fine.
router.ex:
defmodule SimpleServer.Router do
use Plug.Router
use Plug.Debugger
require Logger
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
get "/storage/:key" do
resp_msg = case SimpleServer.Storage.get(key) do
nil -> "The key #{key} doesn't exist!\n"
val -> "The key #{key} has value #{val}.\n"
end
send_resp(conn, 200, resp_msg)
end
post "/storage/set" do
{:ok, body_string, conn} = read_body(conn)
body_map = Poison.decode!(body_string)
IO.inspect(body_map) #This outputs to terminal window where server is running
Enum.each(
body_map,
fn {key, val} -> SimpleServer.Storage.set(key,val) end
)
send_resp(
conn,
201,
"Server stored all key-value pairs\n"
)
end
match _ do
send_resp(conn, 404, "not found")
end
end
The first thing to note in the code above is the route:
get "/storage/:key" do
That will match a path like:
/storage/x
and plug will create a variable named key and assign it the value "x", like this:
key = "x"
Also, note that when you call a function:
width = 10
height = 20
show(width, height)
elixir looks at the function definition:
def show(x, y) do
IO.puts x
IO.puts y
end
and matches the function call to the def like this:
show(width, height)
| |
V V
def show( x , y) do
...
end
and performs the assignments:
x = width
y = height
Then, inside the function you can use the x and y variables. In this line:
Enum.each(
body_map,
# | | | | |
# V V V V V
fn {key, val} -> SimpleServer.Storage.set(key,val) end
)
Elixir will call the anonymous function passing values for key and val, like this:
func("x", "10")
Therefore, in the body of the anonymous function you can use the variables key and val:
SimpleServer.Storage.set(key,val)
because the variables key and val will already have been assigned values.
storage.ex:
defmodule SimpleServer.Storage do
use Agent
def start_link(_args) do #<*** Note the change here
Agent.start_link(fn -> %{} end, name: :tmp_storage)
end
def set(key, value) do
Agent.update(
:tmp_storage,
fn(map) -> Map.put_new(map, key, value) end
)
end
def get(key) do
Agent.get(
:tmp_storage,
fn(map) -> Map.get(map, key) end
)
end
end
application.ex:
defmodule SimpleServer.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
#moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: SimpleServer.Router, options: [port: 8085]),
{SimpleServer.Storage, []}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: SimpleServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
mix.exs:
defmodule SimpleServer.MixProject do
use Mix.Project
def project do
[
app: :simple_server,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {SimpleServer.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:poison, "~> 4.0"},
{:plug_cowboy, "~> 2.0"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
]
end
end
Note, if you use the dependencies and versions specified in the tutorial you will get some warnings, including the warning:
~/elixir_programs/simple_server$ iex -S mix
...
...
12:48:57.767 [warn] Setting Ranch options together
with socket options is deprecated. Please use the new
map syntax that allows specifying socket options
separately from other options.
...which is an issue with Plug. Here are the dependencies and versions that I used to get rid of all the warnings:
{:poison, "~> 4.0"},
{:plug_cowboy, "~> 2.0"}
Also, when you list an application as a dependency, you no longer have to enter it in the :extra_applications list. Elixir will automatically start all the applications listed as dependencies before starting your application. See :applications v. :extra_applications.
Once the server has started, you can use another terminal window to send a post request with curl (or you can use some other program):
~$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"x": "10", "y": "20" }
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> POST /storage/set HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 23
>
* upload completely sent off: 23 out of 23 bytes
< HTTP/1.1 201 Created
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:23 GMT
< content-length: 34
< cache-control: max-age=0, private, must-revalidate
<
Server stored all key-value pairs
* Connection #0 to host localhost left intact
The > lines are the request, and the < lines are the response. Also, check the output in the terminal window where the server is running.
~$ curl -v http://localhost:8085/storage/z
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/z HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:30 GMT
< content-length: 25
< cache-control: max-age=0, private, must-revalidate
<
The key z doesn't exist!
* Connection #0 to host localhost left intact
.
~$ curl -v http://localhost:8085/storage/x
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/x HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:37 GMT
< content-length: 24
< cache-control: max-age=0, private, must-revalidate
<
The key x has value 10.
* Connection #0 to host localhost left intact

I'm not entirely sure what you're trying to accomplish, but the error is telling you that the key and value that are passed to the router with statement are not defined. Elixir thinks you are trying to call a function with those arguments because they are not "bound" to a value. That is why you are seeing warning: variable "value" does not exist and is being expanded to "value()"
I suppose this is not really an answer but maybe more an explanation of the error you're seeing.

You need to pull the key/value params out of your %Plug.Conn{} object (conn). The key/value variables have not yet been defined within the scope of your route. The conn object is only available because it is injected by the post macro provided by Plug.
I am not quite aware of what type of requests you're submitting to the router, but I'll assume it's JSON as an example. You can manually parse the body in your connection by doing something like:
with {:ok, raw_body} <- Plug.Conn.read_body(conn),
{:ok, body} <- Poison.decode(raw_body) do
key = Map.get(body, "key")
value = map.get(body, "value")
# ... other logic
end
The Plug project, however, provides a nice convenience plug for you to parse request bodies in a generic way: Plug.Parsers.
To implement this in your router, you just have to add the plug to the top of your router (below Plug.Logger I think):
plug Plug.Parsers,
parsers: [:urlencoded, :json]
json_decoder: Poison,
pass: ["text/*", "application/json"]
The :urlencoded part will parse your query parameters and the :json part will parse the body of the request.
Then below in your route, you can get the key/value params from your conn object in the :params key like so:
%{params: params} = conn
key = Map.get(params, "key")
value = Map.get(params, "value")
Also, I should note that the best JSON decoder at the moment is Jason which is basically a drop-in replacement for Poison, but faster.
Anyway, reading hexdocs really helps with figuring this stuff out and the Plug project has great documentation. I think Elixir is a great language to start programming with (although it's essential to learn object-oriented paradigms as well). Happy coding!

Related

Angr for a HTB challenge, no solution found

I'm new to RE. I'm trying to solve a HackTheBox challenge called RAuth, with angr. I understand how to analyze and solve this challenge differently, without angr, but I really want to understand what is wrong with my angr script, or maybe what is the reason why angr is not feasible for this (and similar) case?
The application is easy, after it starts it's requesting the password from stdin, and exits if the password is incorrect:
>:~/challenges/rauth$ ./rauth
Welcome to secure login portal!
Enter the password to access the system:
wqeqwwqewqewqeqwewqeqweqweqweqweqwewqeqwe
You entered a wrong password!
When I run my script it works for around 15 minutes and dies with one of two errors:
"Killed"
"IndexError: tuple index out of range"
My angr script:
#!/usr/bin/env python
#coding: utf-8
import angr
import claripy
import time
import sys
def is_successful(st):
output = st.posix.dumps(sys.stdout.fileno())
if b'Successfully Authenticated' in output:
return True
return False
def should_abort(state):
output = state.posix.dumps(sys.stdout.fileno())
return b'You entered a wrong password!' in output
def main(round):
print("Checking "+str(round))
p = angr.Project('rauth',auto_load_libs=False)
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(round)]
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])
st = p.factory.full_init_state(
args=['./rauth'],
add_options=angr.options.unicorn,
stdin=angr.SimPackets(name='stdin', content=[(flag, 32)])
#stdin=flag,
)
st.options.add(angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY)
st.options.add(angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS)
for k in flag_chars:
st.solver.add(k >= ord(" "))
st.solver.add(k <= ord("~"))
sm = p.factory.simulation_manager(st)
#sm.explore(find=is_successful,avoid=should_abort)
sm.explore(find=lambda s: b"Successfully" in s.posix.dumps(1),avoid=lambda s: b"wrong" in s.posix.dumps(1))
if len(sm.found) > 0:
for solution_state in ex.found:
print("[>>] {!r}".format(solution_state.solver.eval(flag,cast_to=bytes)))
else:
print("[>>] no solution found :(")
if __name__ == "__main__":
print(main(32))
Am I using angr for the case when it's not applicable? What am I missing?
A also tried to play with options, like removing unicorn, enabling auto_load_libs, using or disabling lambdas etc. No success.

Karate properties.json throws ReferenceError when Reading Properties in *.feature file

Having updated Karate from 0.6.2 to 0.9.5 recently I've had a number of ReferenceError's w.r.t the properties.json I've used throughout my test cases.
I've the following setup:
test-properties.json
{
"headers": {
"x-client-ip": "192.168.3.1",
"x-forwarded-for": "192.168.3.1"
}
}
test-auth.feature
Background:
* def props = read('properties/test-properties.json')
I then use props further down in my first scenario:
And header User-Agent = props.headers.Accept-Language
And header X-Forwarded-For = props.headers.x-forwarded-for
However, when running this I get the following issue:
com.intuit.karate.exception.KarateException: test-auth.feature:14 - javascript evaluation failed: props.headers.Accept-Language, ReferenceError: "Language" is not defined in <eval> at line number 1
I've tried adding the properties file into the same package as the test-auth.feature to no avail. The issue seems to be with reading the json file. I'm aware Karate 0.6.2 could evaluate the file type and parse it internally in its native format. Is this still the case? If not, what is the solution to reading from properties.json in Karate 0.9.5.
Nothing should have changed when it comes to reading JSON files. Karate evaluates the RHS as JS, so I think this is the solution:
And header User-Agent = props.headers['Accept-Language']
And header X-Forwarded-For = props.headers['x-forwarded-for']
EDIT: this works for me:
* def props = { headers: { 'Accept-Language': 'foo', 'x-forwarded-for': 'bar' } }
* url 'http://httpbin.org/headers'
* header User-Agent = props.headers['Accept-Language']
* header X-Forwarded-For = props.headers['x-forwarded-for']
* method get
Resulting in:
1 > GET http://httpbin.org/headers
1 > Accept-Encoding: gzip,deflate
1 > Connection: Keep-Alive
1 > Host: httpbin.org
1 > User-Agent: foo
1 > X-Forwarded-For: bar
So if you are still stuck, please follow this process: https://github.com/intuit/karate/wiki/How-to-Submit-an-Issue

Postgrex how to define json library

I'm just trying to use Postgrex without any kind of ecto setup, so just the example from the documentation readme.
Here is what my module looks like:
defmodule Receive do
def start(_type, _args) do
{:ok, pid} = Postgrex.start_link(
hostname: "localhost",
username: "john",
# password: "",
database: "property_actions",
extensions: [{Postgrex.Extensions.JSON}]
)
Postgrex.query!(
pid,
"INSERT INTO actions (search_terms) VALUES ($1)",
[
%{foo: 'bar'}
]
)
end
end
when I run the code I get
** (RuntimeError) type `json` can not be handled by the types module Postgrex.DefaultTypes, it must define a `:json` library in its options to support JSON types
Is there something I'm not setting up correctly? From what I've gathered in the documentation, I shouldn't even need to have that extensions line because json is handled by default.
On Postgrex <= 0.13, you need to define your own types:
Postgrex.Types.define(MyApp.PostgrexTypes, [], json: Poison)
and then when starting Postgrex:
Postgrex.start_link(types: MyApp.PostgrexTypes)
On Postgrex >= 0.14 (currently master), it was made easier:
config :postgrex, :json_library, Poison

Get only part of a JSON when using an API on NodeMCU

I am using http.get() to get a JSON from an API I am using, but it's not getting the data. I have the suspicion that this JSON is too big for the NodeMCU. I only need the information in the subpart "stats:". Is it possible to only http.get() that part of the JSON?
EDIT:
This is my code
function getstats()
http.get("https://api.aeon-pool.com/v1/stats_address?address=WmsGUrXTR7sgKmHEqRNLgPLndWKSvjFXcd4soHnaxVjY3aBWW4kncTrRcBJJgUkeGwcHfzuZABk6XK6qAp8VmSci2AyGHcUit", nil, function(code, pool)
if (code < 0) then
print("can't get stats")
else
h = cjson.decode(pool)
hashrate = h[1]["hashrate"]
print(hashrate)
dofile('update_display.lua')
end
end)
end
I also have another function getting data from another api above getstats()
function getaeonrate()
http.get("https://api.coinmarketcap.com/v1/ticker/aeon/?convert=EUR", nil, function(code, dataaeon)
if (code < 0) then
print("can't get aeon")
else
-- Decode JSON data
m = cjson.decode(dataaeon)
-- Extract AEON/EUR price from decoded JSON
aeonrate = string.format("%f", m[1]["price_eur"]);
aeonchange = "24h " .. m[1]["percent_change_24h"] .. "% 7d " .. m[1]["percent_change_7d"] .. "%"
dofile('update_display.lua')
end
end)
end
But now the weird thing is, when I want to access 'pool' from getstats() I get the json data from getaeonrate(). So "hashrate" isn't even in the json because I am getting the json from another function.
I tried making a new project only with getstats() and that doesn't work at all I always get errors like this
HTTP client: Disconnected with error: -9
HTTP client: Connection timeout
HTTP client: Connection timeout
Yesterday I thought that the response was too big from api.aeon-pool.com, I if you look at the json in your webbrowser you can see that the top entry is 'stats:' and I only need that, none of the other stuff. So If the request is to big It would be nice to only http.get() that part of the json, hence my original question. At the moment I am not even sure what is not working correctly, I read that the nodemcu firmware generally had problems with http.get() and that it didn't work correctly for a long time, but getting data from api.coinmarketcap.com works fine in the original project.
The problems with the HTTP module are with near certainty related to https://github.com/nodemcu/nodemcu-firmware/issues/1707 (SSL and HTTP are problematic).
Therefore, I tried with the more bare-bone TLS module on the current master branch. This means you need to manually parse the HTTP response including all headers looking for the JSON content. Besides, you seem to be on an older NodeMCU version as you're still using CJSON - I used SJSON below:
Current NodeMCU master branch
function getstats()
buffer = nil
counter = 0
local srv = tls.createConnection()
srv:on("receive", function(sck, payload)
print("[stats] received data, " .. string.len(payload))
if buffer == nil then
buffer = payload
else
buffer = buffer .. payload
end
counter = counter + 1
-- not getting HTTP content-length header back -> poor man's checking for complete response
if counter == 2 then
print("[stats] done, processing payload")
local beginJsonString = buffer:find("{")
local jsonString = buffer:sub(beginJsonString)
local hashrate = sjson.decode(jsonString)["stats"]["hashrate"]
print("[stats] hashrate from aeon-pool.com: " .. hashrate)
end
end)
srv:on("connection", function(sck, c)
sck:send("GET /v1/stats_address?address=WmsGUrXTR7sgKmHEqRNLgPLndWKSvjFXcd4soHnaxVjY3aBWW4kncTrRcBJJgUkeGwcHfzuZABk6XK6qAp8VmSci2AyGHcUit HTTP/1.1\r\nHost: api.aeon-pool.com\r\nConnection: close\r\nAccept: */*\r\n\r\n")
end)
srv:connect(443, "api.aeon-pool.com")
end
Note that the receive event is fired for every network frame: https://nodemcu.readthedocs.io/en/latest/en/modules/net/#netsocketon
NodeMCU fails to establish a connection to api.coinmarketcap.com due to a TLS handshake failure. Not sure why that is. Otherwise your getaeonrate() could be implemented likewise.
Frozen 1.5.4 branch
With the old branch the net module can connect to coinmarketcap.com.
function getaeonrate()
local srv = net.createConnection(net.TCP, 1)
srv:on("receive", function(sck, payload)
print("[aeon rate] received data, " .. string.len(payload))
local beginJsonString = payload:find("%[")
local jsonString = payload:sub(beginJsonString)
local json = cjson.decode(jsonString)
local aeonrate = string.format("%f", json[1]["price_eur"]);
local aeonchange = "24h " .. json[1]["percent_change_24h"] .. "% 7d " .. json[1]["percent_change_7d"] .. "%"
print("[aeon rate] aeonrate from coinmarketcap.com: " .. aeonrate)
print("[aeon rate] aeonchange from coinmarketcap.com: " .. aeonchange)
end)
srv:on("connection", function(sck, c)
sck:send("GET /v1/ticker/aeon/?convert=EUR HTTP/1.1\r\nHost: api.coinmarketcap.com\r\nConnection: close\r\nAccept: */*\r\n\r\n")
end)
srv:connect(443, "api.coinmarketcap.com")
end
Conclusion
The HTTP module and TLS seem a no-go for your APIs due to a bug in the firmware (1707).
The net/TLS module of the current master branch manages to connect to api.aeon-pool.com but not to api.coinmarketcap.com.
With the old and frozen 1.5.4 branch it's exactly the other way around.
There may (also) be issues with cipher suits that don't match between the firmware and the API provider(s).
-> :( no fun like that

IRC bot pong doesn't work

I've created bot, using code from this page.
Everything was good, when I was trying to reach irc.rizon.net. But problem arrives, when I've changed server to irc.alphachat.net.
#!/usr/bin/env python3
import socket
server = 'irc.alphachat.net'
channel = '#somechannel'
NICK = 'somenick'
IDENT = 'somenick'
REALNAME = 'somenick'
port = 6667
def joinchan(chan):
ircsock.send(bytes('JOIN %s\r\n' % chan, 'UTF-8'))
def ping(): # This is our first function! It will respond to server Pings.
ircsock.send(bytes("QUOTE PONG \r\n", 'UTF-8'))
def send_message(chan, msg):
ircsock.send(bytes('PRIVMSG %s :%s\r\n' % (chan, msg), 'UTF-8'))
ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ircsock.connect((server, port)) # Here we connect to the server using the port 6667
ircsock.send(bytes("USER "+ NICK +" "+ NICK +" "+ NICK +" :This bot\n", 'UTF-8')) # user authentication
ircsock.send(bytes("NICK "+ NICK +"\n", 'UTF-8')) # here we actually assign the nick to the bot
joinchan(channel) # Join the channel using the functions we previously defined
while 1: # Be careful with these! it might send you to an infinite loop
ircmsg = ircsock.recv(2048).decode() # receive data from the server
ircmsg = ircmsg.strip('\n\r') # removing any unnecessary linebreaks.
print(ircmsg) # Here we print what's coming from the server
if ircmsg.find(' PRIVMSG ')!=-1:
nick=ircmsg.split('!')[0][1:]
if ircmsg.find("PING :") != -1: # if the server pings us then we've got to respond!
ping()
if ircmsg.find(":Hello "+ NICK) != -1: # If we can find "Hello Mybot" it will call the function hello()
hello()
Problem is with ping command because I don't know how to answer to server:
:irc-us2.alphachat.net NOTICE * :*** Looking up your hostname...
:irc-us2.alphachat.net NOTICE * :*** Checking Ident
:irc-us2.alphachat.net NOTICE * :*** Found your hostname
:irc-us2.alphachat.net NOTICE * :*** No Ident response
PING :CE661578
:irc-us2.alphachat.net 451 * :You have not registered
With IRC, you should really split each line up by ' ' (space) into chunks to process it - something like this should work after your print (untested)
The reason it's not working is because you're not replying to PINGs properly
chunk = ircmsg.split(' ')
if chunk[0] == 'PING': # This is a ping
ircsock.send(bytes('PONG :%s\r\n' % (chunk[1]), 'UTF-8')) # Send a pong!
if chunk[1] == 'PRIVMSG': # This is a message
if chunk[3] == ':Hello': # Hey, someone said hello!
send_message(chunk[2], "Hi there!") # chunk[2] is channel / private!
if chunk[1] == '001': # We've logged on
joinchannel(channel) # Let's join!
send_message(channel, "I've arrived! :-)") # Announce to the channel
Normally the command / numeric is found in the second parameter (chunk[1]) - The only exception I can think of is PING which is found in the first (chunk[0])
Also note that I moved joinchannel() - you should only be doing this after you're logged on.
Edit: Didn't realise the age of this post. Sorry!
I believe you just need to make a small change to the string you send in response to the ping request.
try using:
ircsock.send(bytes("PONG pingis\n", "UTF-8"))
This ping response works for me on freenode.