1use strict;
2use Irssi 20020300;
3use 5.6.0;
4use Socket;
5use POSIX;
6
7use vars qw($VERSION %IRSSI %HELP);
8$HELP{sms} = "
9SMS <handle or phone number> <text>
10
11Sends sms to handle from addressbook (see HELP addsms and listsms)
12or phone number.
13";
14$HELP{addsms} = "
15ADDSMS <handle> <phone number>
16
17Adds 'handle' with phone 'phone number' to addressbook,
18or change phone number of existing handle.
19";
20$HELP{delsms} = "
21DELSMS <handle or number from listsms>
22
23Deletes entry from addressbook.
24";
25$HELP{listsms} = "
26LISTSMS [handle match]
27
28Lists addressbook.
29";
30$VERSION = "1.5b";
31%IRSSI = (
32        authors         => "Maciek \'fahren\' Freudenheim",
33        contact         => "fahren\@bochnia.pl",
34        name            => "SMS",
35        description     => "/ADDSMS, /DELSMS, /LISTSMS and /SMS - phone address-book with smssender, for now supports only Polish operators",
36        license         => "GNU GPLv2 or later",
37        changed         => "Fri Jan 10 03:54:07 CET 2003"
38);
39
40Irssi::theme_register([
41	'sms_sending', '>> Sending SMS to %_$0%_ /$1/',
42	'sms_sent', '>> Message to %_$0%_ has been sent.',
43	'sms_esent', '>> Message $1/$2 to %_$0%_ has been sent.',
44	'sms_notsent', '>> Message to %_$0%_ has %_NOT%_ been sent.',
45	'sms_enotsent', '>> Message $1/$2 to %_$0%_ has %_NOT%_ been sent.',
46	'sms_stat', '>> Total of %_$0%_ entries: %_$1%_ from PLUS, %_$2%_ from ERA, %_$3%_ from IDEA.',
47	'sms_listline', '[%W$[!-2]0%n]%| $[9]1%_:%_ $2 /$[-4]3/',
48]);
49
50# Chanelog:
51## version 1.5b
52# $ENV{HOME}/.irssi -> Irssi::get_irssi_dir
53## version 1.5
54# - added new prefixes
55## version 1.4
56# - sorting /smslist
57# - do not lowercasing handles
58## version 1.3
59# - fixed smsfork(), ifork()
60# - added help
61## version 1.2d
62# - ... more ERA() fixes
63## version 1.2c
64# - added parsing of 'request cannot be processed at this time' in IDEA()
65# - added [act/total] in ERA() split messages
66## version 1.2b
67# - more fixes in ERA() messages spliting
68## version 1.2
69# - fixed long message spliting in ERA()
70## version 1.1
71# - fixed IDEA()
72# - inf. ifork() loop fixed (found by Lam)
73# - fixed regex matching in /delsms, /listsms and /sms
74# - changed kill() to POSIX::_exit()
75## version 1.0
76# - forking before sending SMS
77
78my $smssender = getlogin || getpwuid($<) || "anonymous";
79my $smsfile = Irssi::get_irssi_dir . "/smslist";
80my (@smslist, $fh, %ftag);
81
82sub cmd_sms {
83	my ($target, $text) = split(/ +/, $_[0], 2);
84	my $window = Irssi::active_win();
85	my $phone;
86
87	if ($text eq "") {
88		Irssi::print("Usage: /SMS <handle or phone number> <text>");
89		return;
90	}
91
92	if (isnumber($target)) {
93		if ($phone = corrnum($target)) {
94			my $net = smsnet($phone);
95			$window->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_sending', smsnum($phone), $net);
96			&$net($phone, $text) unless &smsfork;
97		} else {
98			Irssi::print("%R>>%n Wrong number.");
99		}
100	} else {
101		my $i = 0;
102		my $handle = lc($target);
103		my $all = $handle eq "*"? 1 : 0;
104		for my $sms (@smslist) {
105			next unless ($all || lc($sms->{handle}) eq $handle);
106			$i++;
107			my $net = smsnet($sms->{phone});
108			$window->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_sending', $sms->{handle}, $net);
109			&$net($sms->{phone}, $text) unless &smsfork;
110		}
111		Irssi::print("%R>>%n Can't find %_$target%_ in address book.") unless $i;
112	}
113}
114
115sub cmd_addsms {
116	my ($handle, $num) = split(/ +/, $_[0], 2);
117	my $phone;
118
119	unless ($phone = corrnum($num)) {
120		Irssi::print("Usage: /ADDSMS <handle> <phone number>");
121		return;
122	}
123
124	for my $sms (@smslist) {
125		if (lc($sms->{handle}) eq lc($handle)) {
126			Irssi::print(">> Changing phone number for %_$handle%_ /to $phone/");
127			$sms->{phone} = $phone;
128			&savesms;
129			return;
130		}
131	}
132	my $sms = {};
133	$sms->{handle} = $handle;
134	$sms->{phone}  = $phone;
135	Irssi::print(">> Adding %_$handle%_ with num %_$phone%_.");
136	push @smslist, $sms;
137	&savesms;
138}
139
140sub cmd_delsms {
141	my $handle = shift;
142
143	if ($handle eq "") {
144		Irssi::print("Usage: /DELSMS <handle or number from listsms>");
145		return;
146	}
147
148	my @num;
149	$handle = lc($handle);
150
151	if ($handle =~ /^[0-9]+$/) {
152		push @num, $handle - 1;
153	} else {
154		my $all = $handle eq "*"? 1 : 0;
155		@smslist = sort { lc($a->{handle}) cmp lc($b->{handle}) } @smslist;
156		for (my $i = 0; $i < @smslist; $i++) {
157			push @num, $i if ($all || lc($smslist[$i]->{handle}) eq $handle);
158		}
159	}
160	for my $n (reverse(@num)) {
161		if (my($sms) = splice(@smslist, $n, 1)) {
162			Irssi::print(">> Deleted %_$sms->{handle}%_.");
163		}
164	}
165
166	&savesms;
167}
168
169sub cmd_listsms {
170	my $match = shift || "*";
171	my $window = Irssi::active_win();
172
173	if (@smslist == 0) {
174		Irssi::print("%R>>%n Your SMSLIST is empty.");
175		return;
176	}
177	my $all = $match eq "*"? 1 : 0;
178	@smslist = sort { lc($a->{handle}) cmp lc($b->{handle}) } @smslist;
179	my $i = 1;
180	for my $sms (@smslist) {
181		next unless $all || $sms->{handle} =~ /\Q$match\E/i;
182		$window->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_listline', $i++, $sms->{handle}, $sms->{phone}, smsnet($sms->{phone}));
183	}
184	&smsstat if $match eq "*";
185}
186
187sub smsstat {
188	my ($plus, $era, $idea) = (0, 0, 0);
189
190	for my $sms (@smslist) {
191		for ($sms->{phone}) {
192			/^6(0[1,3,5,7,9]|91)/ and $plus++;
193			/^6(0[0,2,4,6,8]|92)/ and $era++;
194			/^50/ and $idea++;
195		}
196	}
197	Irssi::active_win()->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_stat', scalar(@smslist), $plus, $era, $idea);
198}
199
200sub savesms {
201	local *fp;
202	open (fp, ">", $smsfile) or die "Couldn't open $smsfile for writing";
203	for my $sms (@smslist) {
204		print(fp "$sms->{handle} $sms->{phone}\n");
205	}
206	close(fp);
207}
208
209sub loadsms {
210	@smslist = ();
211	return unless (-e $smsfile);
212	local *fp;
213	open(fp, "<", $smsfile);
214	local $/ = "\n";
215	while (<fp>) {
216		chop;
217		my $sms = {};
218		($sms->{handle}, $sms->{phone}) = split(/ /);
219		push(@smslist, $sms);
220	}
221	close(fp);
222	Irssi::print("Loaded address book:");
223	&smsstat;
224}
225
226sub isnumber {
227	return ($_[0] =~ /^([+]|[0-9])[0-9]{6,}$/);
228}
229
230sub corrnum {
231	my $num = shift;
232
233	return 0 unless isnumber($num);
234
235	if ($num =~ /^\+/) {
236		return 0 unless $num =~ s/^(\+48)//g;
237	}
238	$num =~ s/^(48)//;
239
240	return $num;
241}
242
243sub smsnum {
244	my $num = shift;
245	for my $sms (@smslist) {
246		if ($sms->{phone} eq $num) {
247			return $sms->{handle}
248		}
249	}
250	return $num;
251}
252
253sub smsnet {
254	for (@_) {
255		/^6(0[13579]|9[135])/ and return "PLUS";
256		/^6(0[02468]|9[24])/ and return "ERA";
257		/^50/ and return "IDEA";
258	}
259	return "UNKNOWN";
260}
261
262sub urlencode {
263	my $ret = shift;
264	$ret =~ s/([^a-zA-Z0-9])/sprintf("%%%.2x", ord($1));/eg;
265	return $ret;
266}
267
268sub smsfork {
269	my ($rh, $wh);
270	pipe($rh, $wh);
271	my $pid = fork();
272	unless (defined $pid) {
273		Irssi::print("%R>>%n Failed to fork() :/ -  $!");
274		close $rh; close $wh;
275		return 1;
276	} elsif ($pid) { 	# parent
277		close $wh;
278		$ftag{$rh} = Irssi::input_add(fileno($rh), INPUT_READ, \&ifork, $rh);
279		Irssi::pidwait_add($pid);
280	} else { 		# child
281		close $rh;
282		$fh = $wh;
283	}
284	return $pid;
285}
286
287sub smskill {
288	print($fh "finished\n");
289	close $fh;
290	POSIX::_exit(1);
291}
292
293sub ifork {
294	my $rh= shift;
295	my $ret = 0;
296	while (<$rh>) {
297		/^sent (.+)/ and Irssi::active_win()->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_sent', smsnum($1)), last;
298		/^esent ([0-9]+)\s([0-9]+)\s([0-9]+)$/ and Irssi::active_win()->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_esent', smsnum($1), $2, $3), last;
299		/^notsent (.+)/ and Irssi::active_win()->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_notsent', smsnum($1)), last;
300		/^enotsent ([0-9]+)\s([0-9]+)\s([0-9]+)$/ and Irssi::active_win()->printformat(MSGLEVEL_CLIENTNOTICE, 'sms_enotsent', smsnum($1), $2, $3), last;
301		/^info (.+)/ and Irssi::print("$1"), last;
302		/^finished$/ and $ret = 1, last;
303	}
304	return unless $ret;
305	Irssi::input_remove($ftag{$rh});
306	delete $ftag{$rh};
307	close $rh;
308}
309
310sub sconnect {
311	my $target = shift;
312	my ($proto, $iaddr, $saddr);
313
314	$proto = getprotobyname('tcp');
315	$iaddr = inet_aton($target);
316	socket(SOCK, PF_INET, SOCK_STREAM, $proto) || return 0;
317	local $SIG{ALRM} = sub {
318		print($fh "info %R>>%n connect() to $target timeouted :/\n");
319		close SOCK;
320		return 0;
321	};
322	alarm 10;
323	unless (connect(SOCK, sockaddr_in(80, $iaddr))) {
324		print($fh "info %R>>%n Couldn't connect to $target: $!\n");
325		close SOCK;
326		return 0;
327	}
328	alarm 0;
329	my $old = select(SOCK); $| = 1; select($old);
330	return 1;
331}
332
333sub PLUS {
334	my ($phone, $text) = @_;
335	&smskill unless sconnect("sms.plusgsm.pl");
336	my $tosend = "tprefix=" . substr($phone, 0, 3) . "&numer=" . substr($phone, 3) . "&odkogo=$smssender&tekst=" . urlencode($text) . "&dzien=dzisiaj&godz=&min=";
337	print SOCK "POST /sms/sendsms.php HTTP/1.0\n";
338	print SOCK "Host: www.text.plusgsm.pl:80\n";
339	print SOCK "Accept: */*\n";
340	print SOCK "Content-type: application/x-www-form-urlencoded\n";
341	print SOCK "Content-length: " . length($tosend) . "\n\n";
342	print SOCK "$tosend\r\n";
343	while (<SOCK>) {
344		/wiadomo�� zosta�a wys�ana/ and print($fh "sent $phone\n"), last;
345		/nie zosta� wys�any/ and print($fh "notsent $phone\n"), last;
346	}
347	close SOCK;
348	&smskill;
349}
350
351sub ERA {
352	my ($phone, $cutme) = @_;
353	my $ml = 126 - length($smssender);
354	my $cl = length($cutme);
355	my $total = int($cl / $ml) + (($cl%$ml)? 1 : 0);
356	if ($total > 1) {
357		$ml -= (4 + length($total) * 2);
358		$total = int($cl / $ml) + (($cl%$ml)? 1 : 0);
359		printf($fh "info >> Spliting SMS to $total messages.\n");
360	}
361	my $act = 0;
362	while ($cutme =~ s/.{1,$ml}//) {
363		my ($cookie, $code, $tosend, $text);
364		&smskill unless sconnect("boa.eragsm.pl");
365		$act++;
366		$text = "<$act/$total> " if $total > 1;
367		$text .= $&;
368		print SOCK "POST /sms/sendsms.asp?sms=1 HTTP/1.0\n";
369		print SOCK "Host: boa.eragsm.com.pl:80\n";
370		print SOCK "Accept: */*\n\r\n";
371		while (<SOCK>) {
372			$cookie = $1 if /Set\-Cookie\:\ ([^\;]+?)\;/;
373			$code = $1 if /name\=\"Code\"\ value\=\"(.+?)\"/;
374		}
375		close SOCK;
376		$tosend = "numer=$phone&bookopen=&message=" . urlencode($text) . "&podpis=$smssender&kontakt=&Nadaj=Nadaj&code=$code&Kasuj=Kasuj&Telefony=Telefony";
377		&smskill unless sconnect("boa.eragsm.pl");
378		print SOCK "POST /sms/sendsms.asp HTTP/1.0\n";
379		print SOCK "Host: boa.eragsm.com.pl:80\n";
380		print SOCK "Accept: */*\n";
381		print SOCK "Cookie: $cookie\n";
382		print SOCK "Referer: http://boa.eragsm.com.pl/sms/sendsms.asp\n";
383		print SOCK "Content-type: application/x-www-form-urlencoded\n";
384		print SOCK "Content-length: " . length($tosend) . "\n\n";
385		print SOCK "$tosend\r\n";
386		if ($total > 1) {
387			while (<SOCK>) {
388				/nie zosta�a wys�ana!/ and print($fh "enotsent $phone $act $total\n"), last;
389				/zosta�a wys�ana/ and print($fh "esent $phone $act $total\n"), last;
390			}
391		} else {
392			while (<SOCK>) {
393				/nie zosta�a wys�ana!/ and print($fh "notsent $phone\n"), last;
394				/zosta�a wys�ana/ and print($fh "sent $phone\n"), last;
395			}
396		}
397		close SOCK;
398	}
399	&smskill;
400}
401
402sub IDEA {
403	my ($phone, $text) = @_;
404	my ($sec, $min, $hour, $day, $mon, $year) = (localtime)[0,1,2,3,4,5];
405	$year += 1900;
406	$mon += 1;
407	&smskill unless sconnect("sms.idea.pl");
408        my $tosend = "LANGUAGE=pl&NETWORK=smsc1&DELIVERY_TIME=0&SENDER=$smssender&RECIPIENT=$phone&VALIDITY_PERIOD=24&DELIVERY_DATE=$day&DELIVERY_MONTH=$mon&DELIVERY_YEAR=$year&DELIVERY_HOUR=$hour&DELIVERY_MIN=$min&NOTIFICATION_FLAG=false&NOTIFICATION_ADDRESS=&SHORT_MESSAGE=" . urlencode($text) . "&SUBMIT=Wyslij";
409	print SOCK "POST /sendsms.asp HTTP/1.0\n";
410	print SOCK "Host: sms.idea.pl:80\n";
411	print SOCK "Accept: */*\n";
412	print SOCK "Content-type: application/x-www-form-urlencoded\n";
413	print SOCK "Content-length: " . length($tosend) . "\n\n";
414	print SOCK "$tosend\r\n";
415	while (<SOCK>) {
416		/SMS nie zostanie/ and print($fh "notsent $phone\n"), last;
417		/doby zosta� wyczerpany/ and print($fh "notsent $phone\n"), last;
418		/zosta�a wys�ana/ and print($fh "sent $phone\n"), last;
419		/request cannot be processed/ and print($fh "notsent $phone\n"), last;
420	}
421	close SOCK;
422	&smskill;
423}
424
425sub UNKNOWN {
426	print($fh "info %R>>%n Sorry, sms.pl supports only polish operators :/\n");
427	&smskill;
428}
429
430&loadsms;
431
432Irssi::command_bind("sms", "cmd_sms");
433Irssi::command_bind("addsms", "cmd_addsms");
434Irssi::command_bind("smsadd", "cmd_addsms");
435Irssi::command_bind("smsdel", "cmd_delsms");
436Irssi::command_bind("delsms", "cmd_delsms");
437Irssi::command_bind("listsms", "cmd_listsms");
438Irssi::command_bind("smslist", "cmd_listsms");
439Irssi::command_bind("smsstat", "smsstat");
440