comparison after5.rb @ 84:f67f5304baac draft

Add feature of sending message to a user
author HIROSE Yuuji <yuuji@gentei.org>
date Thu, 12 Dec 2013 19:57:20 +0900
parents 54d85f7d5676
children e2b6a2e8b5c7
comparison
equal deleted inserted replaced
83:54d85f7d5676 84:f67f5304baac
2 # -*- coding: euc-jp -*- 2 # -*- coding: euc-jp -*-
3 # 3 #
4 # Associative Scheduling Table - after5 4 # Associative Scheduling Table - after5
5 # (C)2003, 2004, 2006, 2008, 2012, 2013 by HIROSE Yuuji [yuuji<at>gentei.org] 5 # (C)2003, 2004, 2006, 2008, 2012, 2013 by HIROSE Yuuji [yuuji<at>gentei.org]
6 # $Id: after5.rb,v 1.20 2012/12/03 15:54:20 yuuji Exp $ 6 # $Id: after5.rb,v 1.20 2012/12/03 15:54:20 yuuji Exp $
7 # Last modified Wed Dec 11 08:38:14 2013 on firestorm 7 # Last modified Thu Dec 12 18:09:01 2013 on firestorm
8 # See http://www.gentei.org/~yuuji/software/after5/ 8 # See http://www.gentei.org/~yuuji/software/after5/
9 # このスクリプトはEUCで保存してください。 9 # このスクリプトはEUCで保存してください。
10 $hgid = <<_HGID_.split[1..-2].join(" ") 10 $hgid = <<_HGID_.split[1..-2].join(" ")
11 $HGid$ 11 $HGid$
12 _HGID_ 12 _HGID_
605 def members(grp) 605 def members(grp)
606 @groupmap[grp] and ####################@groupmap[grp]['members'] 606 @groupmap[grp] and ####################@groupmap[grp]['members']
607 collectmembers(grp) 607 collectmembers(grp)
608 end 608 end
609 def membernames(grp) 609 def membernames(grp)
610 members(grp).collect{|u| nickname(u)} 610 if isgroup(grp)
611 members(grp).collect{|u| nickname(u)}
612 else
613 [nickname(grp)]
614 end
611 end 615 end
612 def admins(grp) 616 def admins(grp)
613 @groupmap[grp] and @groupmap[grp]['admin'] 617 @groupmap[grp] and @groupmap[grp]['admin']
614 end 618 end
615 def groupname(grp) 619 def groupname(grp)
1377 'joinmyself' => ['自分自身が既存のグループに対して入る(IN)か出る(OUT)かを決めるのがここ。自分管理のグループに誰かを足すなら「管理操作」、新たにグループを作るなら', 1381 'joinmyself' => ['自分自身が既存のグループに対して入る(IN)か出る(OUT)かを決めるのがここ。自分管理のグループに誰かを足すなら「管理操作」、新たにグループを作るなら',
1378 'In this page, you can decide put yourself IN or OUT of the existing groups. If you want to manage the member of your own group, go to'], 1382 'In this page, you can decide put yourself IN or OUT of the existing groups. If you want to manage the member of your own group, go to'],
1379 'aboutgroup' => ['グループ %s の操作', "Operations on group `%s'"], 1383 'aboutgroup' => ['グループ %s の操作', "Operations on group `%s'"],
1380 'groupwarn' => ['自分が参加してないグループAに、自分が参加しているグループBが含まれている場合、グループAにも加入していると見なされるので気をつけよう。管理者はグループのニックネームを変えられるよ。', 1384 'groupwarn' => ['自分が参加してないグループAに、自分が参加しているグループBが含まれている場合、グループAにも加入していると見なされるので気をつけよう。管理者はグループのニックネームを変えられるよ。',
1381 'Though you are not member of group A, you are treated as a member of A, if you join to the group B, which is a member of A. Think the nesting of groups carefully, please. Group administrator can change the group nickname.'], 1385 'Though you are not member of group A, you are treated as a member of A, if you join to the group B, which is a member of A. Think the nesting of groups carefully, please. Group administrator can change the group nickname.'],
1386 'address2send' => ['自分が参加しているグループのメンバーリストの先頭が自分。その直後にある入力欄には、そのML宛メッセージをどの宛先に配送するかを入れられる。そう、MLごとに自分への配送先を変えられるよ。',
1387 'The first entry of member list of a group to which you belongs, is you. Entry box just after your name is for address list you want to deliver messages to that ML. Thus, you can define different addresses for each ML.'],
1382 'wholemembers' => ['グループ内グループを考慮した上で、現在グループ %s への通知は以下のメンバーに送られる。', 1388 'wholemembers' => ['グループ内グループを考慮した上で、現在グループ %s への通知は以下のメンバーに送られる。',
1383 "Consiering the groups registered in another group, notification to the group `%s' is send to members as follows."], 1389 "Consiering the groups registered in another group, notification to the group `%s' is send to members as follows."],
1384 'noadmingroup' => ['管理できるグループはないっす', 1390 'noadmingroup' => ['管理できるグループはないっす',
1385 "'There's no groups under your administration."], 1391 "'There's no groups under your administration."],
1386 'nickname' => ['ニックネーム', 'nickname'], 1392 'nickname' => ['ニックネーム', 'nickname'],
1454 string.gsub(/\+/, ' ').gsub(/%(..)/){[$1.hex].pack("c")} 1460 string.gsub(/\+/, ' ').gsub(/%(..)/){[$1.hex].pack("c")}
1455 end 1461 end
1456 def escape(string) 1462 def escape(string)
1457 string.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;'). 1463 string.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').
1458 gsub(/>/n, '&gt;').gsub(/</n, '&lt;') 1464 gsub(/>/n, '&gt;').gsub(/</n, '&lt;')
1465 end
1466 def quoted(string)
1467 NKF.nkf('-eMQ', string)
1468 end
1469 def unquoted(string)
1470 NKF.nkf('-emQ', string)
1459 end 1471 end
1460 1472
1461 def gencookie(name, a, expire) 1473 def gencookie(name, a, expire)
1462 x = a.collect{|k, v| 1474 x = a.collect{|k, v|
1463 sprintf("%s=%s", k, encode(v)) if v 1475 sprintf("%s=%s", k, encode(v)) if v
1665 open(seqfile, "r"){|s|s.gets.to_i+1} 1677 open(seqfile, "r"){|s|s.gets.to_i+1}
1666 : 1 1678 : 1
1667 end 1679 end
1668 def sendMail(to, subject, body, from=nil, rcptto=nil, header={}, 1680 def sendMail(to, subject, body, from=nil, rcptto=nil, header={},
1669 thru=nil, spoolto=false) 1681 thru=nil, spoolto=false)
1682 # rcptto should be an Array
1670 body = NKF.nkf("-j", body) unless thru 1683 body = NKF.nkf("-j", body) unless thru
1671 subject = NKF.nkf("-jM", subject.strip) 1684 subject = NKF.nkf("-jM", subject.strip)
1672 to = safecopy(to) # cleanup tainted address 1685 to = safecopy(to) # cleanup tainted address
1673 subject.gsub!(/\n/, '') 1686 subject.gsub!(/\n/, '')
1674 begin 1687 begin
2714 break 2727 break
2715 ## when /^(subject|from): /i 2728 ## when /^(subject|from): /i
2716 when /^(\S+): /i # if new header comes 2729 when /^(\S+): /i # if new header comes
2717 if /^subject:/i =~ hold[0] # check previous header in hold space 2730 if /^subject:/i =~ hold[0] # check previous header in hold space
2718 sj = hold.join.toeuc.sub("Subject: ", "").gsub(tag, "").strip 2731 sj = hold.join.toeuc.sub("Subject: ", "").gsub(tag, "").strip
2719 removeregexp and sj.gsub!(removeregexp, "") 2732 removeregexp && sj && sj.gsub!(removeregexp, "")
2720 sj = sj.sub(/^(re: *)+/i, "Re: ").gsub("\n", "") 2733 sj = sj.sub(/^(re: *)+/i, "Re: ").gsub("\n", "")
2721 hold = ["Subject: "+NKF.nkf('-jM', tag+" "+sj).strip+"\n"] 2734 hold = ["Subject: "+NKF.nkf('-jM', tag+" "+sj).strip+"\n"]
2722 elsif /^from/i =~ hold[0] && fromhack.is_a?(String) 2735 elsif /^from/i =~ hold[0] && fromhack.is_a?(String)
2723 from = hold.join.toeuc.sub(/From: */i, "").strip 2736 from = hold.join.toeuc.sub(/From: */i, "").strip
2724 email, comment = parseaddress(from) 2737 email, comment = parseaddress(from)
2738 if (!comment || comment=="") && comment = @sc.ismembersemail(email)
2739 # Reverse conversion of uname<->email
2740 comment = @sc.nickname(comment) || ""
2741 end
2725 hold = ["From: "+rewritefrom(email, comment, fromhack)+"\n"] 2742 hold = ["From: "+rewritefrom(email, comment, fromhack)+"\n"]
2726 end 2743 end
2727 ret += hold 2744 ret += hold
2728 hold = [line] 2745 hold = [line]
2729 when /^\s/ # continued line 2746 when /^\s/ # continued line
2740 def list() 2757 def list()
2741 # For debug: 2758 # For debug:
2742 # LOCAL=1 DEFAULT=name ./after5.rb -list 2759 # LOCAL=1 DEFAULT=name ./after5.rb -list
2743 # $DEFAULT is ML name 2760 # $DEFAULT is ML name
2744 viamail = ENV['LOCAL'] && ENV['DEFAULT'] # called via mail 2761 viamail = ENV['LOCAL'] && ENV['DEFAULT'] # called via mail
2745 from = toadmin = nil 2762 from = toadmin = groupmode = fromhack = nil
2746 unless @opt['mailprefix'] && @opt['maildomain'] 2763 unless @opt['mailprefix'] && @opt['maildomain']
2747 if viamail 2764 if viamail
2748 STDERR.print msg('sendall_err') % [@opt['conf']] 2765 STDERR.print msg('sendall_err') % [@opt['conf']]
2749 exit 0 2766 exit 0
2750 else 2767 else
2752 return true 2769 return true
2753 end 2770 end
2754 end 2771 end
2755 if viamail then 2772 if viamail then
2756 prohibitviahttp() 2773 prohibitviahttp()
2757 name = ENV['DEFAULT'] 2774 name = unquoted(ENV['DEFAULT'])
2758 if Regexp.new("(.*)("+Regexp.quote(@mailadmsuffix)+")") =~ name 2775 if Regexp.new("(.*)("+Regexp.quote(@mailadmsuffix)+")") =~ name
2759 # To: GROUP/adm*@domain 2776 # To: GROUP/adm*@domain
2760 # -> Forward to group administrator(s) 2777 # -> Forward to group administrator(s)
2761 name, toadmin = $1, $2 2778 name, toadmin = $1, $2
2762 sendMail("dummy", 'dummy', # Original To: and Subject: go through 2779 sendMail("dummy", 'dummy', # Original To: and Subject: go through
2763 STDIN.readlines.join, nil, @sc.admins(name), 2780 STDIN.readlines.join, nil, @sc.admins(name),
2764 {"Return-path" => @opt['maintainer']}, :thru) 2781 {"Return-path" => @opt['maintainer']}, :thru)
2765 exit 0 2782 exit 0
2766 end 2783 end
2767 unless grepgroup(name) 2784 if @sc.isuser(name) # groupmode = nil
2785 # First, compare with username
2786 # groupmode = nil
2787 # Then, check group name
2788 elsif grepgroup(name)
2789 groupmode = true
2790 else # not found
2768 sendMail(@opt['maintainer'], "no group", 2791 sendMail(@opt['maintainer'], "no group",
2769 sprintf("Invalid group address: %s(%s@%s)\nSent by %s\n" + 2792 sprintf("Invalid group address: %s(%s@%s)\nSent by %s\n" +
2770 "URL: %s\n------------\n", 2793 "URL: %s\n------------\n",
2771 name, ENV['LOCAL'], ENV['HOST'], ENV['SENDER'], 2794 name, ENV['LOCAL'], ENV['HOST'], ENV['SENDER'],
2772 @opt['url']) + 2795 @opt['url']) +
2773 "> "+STDIN.readlines.join("> ")) 2796 "> "+STDIN.readlines.join("> "))
2774 exit 0 # should exit 0 in mail mode 2797 exit 0 # should exit 0 in mail mode
2775 end 2798 end
2776 else # via http 2799 else # via http
2777 return nil unless checkauth 2800 return nil unless checkauth
2778 name = @params['name'].untaint 2801 name = unquoted(@params['name'].untaint)
2779 unless grepgroup(name) 2802 if @sc.isuser(name)
2803 # groupmode = nil
2804 elsif grepgroup(name)
2805 groupmode = true
2806 else
2780 @O.print @H.p("No such group: #{name}") 2807 @O.print @H.p("No such group: #{name}")
2781 return true 2808 return true
2782 end 2809 end
2783 nick = @sc.nickname(@params['user']) 2810 nick = @sc.nickname(@params['user'])
2784 from = sprintf("%s <%s>", nick, @params['user']) 2811 from = sprintf("%s <%s>", nick, @params['user'])
2785 body = @params['body'].gsub("\r", "").untaint 2812 body = @params['body'].gsub("\r", "").untaint
2786 end 2813 end
2787 bracket = @sc.getgroupattr(name, 'subjtag') || @opt['mailbracket'] 2814
2788 fromhack = @sc.getgroupattr(name, 'fromhack') 2815 # Set values for header rewriting
2789 xmlname = @sc.getgroupattr(name, 'xmlname') || name 2816 if groupmode # Run as ML
2790 mldir = "ml/"+name 2817 bracket = @sc.getgroupattr(name, 'subjtag') || @opt['mailbracket']
2791 to = @sc.getgroupattr(name, 'mladdress') || defaultmladdress(name) 2818 xmlname = @sc.getgroupattr(name, 'xmlname') || name
2819 mldir = "ml/"+name
2820 to = @sc.getgroupattr(name, 'mladdress') || defaultmladdress(name)
2821 if @sc.getgroupattr(name, 'fromhack')
2822 fromhack = to
2823 end
2824 spooling = @opt['mlspooling']
2825 else # Run as p2p mail
2826 bracket = "NONE" # Throught Subject
2827 user = @params['user'] || ENV['SENDER']
2828 if @sc.ismembersemail(user)
2829 sender = defaultmladdress(quoted(user))
2830 else
2831 sender = user
2832 end
2833 ###fromhack = sprintf("%s <%s>", nick, sender)
2834 fromhack = sender
2835
2836 xmlname = name
2837 to = name
2838 spooling = mldir = nil
2839 end
2792 returnpath = to.sub("@", @mailadmsuffix+"-@") 2840 returnpath = to.sub("@", @mailadmsuffix+"-@")
2793 adminaddr = to.sub("@", @mailadmsuffix+"@") 2841 adminaddr = to.sub("@", @mailadmsuffix+"@")
2794 subj = @params['subject'] || "Message from "+@myname 2842 subj = @params['subject'] || "Message from "+@myname
2795 if bracket == "NONE" 2843 if bracket == "NONE"
2796 sjtag = "" 2844 sjtag = ""
2805 gsub(/%(\d*)c/, '\d+') 2853 gsub(/%(\d*)c/, '\d+')
2806 tagre = Regexp.new(tagpt) 2854 tagre = Regexp.new(tagpt)
2807 subj = sjtag.strip+" "+subj.gsub(Regexp.new(tagpt), "") 2855 subj = sjtag.strip+" "+subj.gsub(Regexp.new(tagpt), "")
2808 end 2856 end
2809 if viamail then 2857 if viamail then
2810 body = tagify_subj(STDIN.readlines, sjtag, tagre, 2858 body = tagify_subj(STDIN.readlines, sjtag, tagre, fromhack).join
2811 fromhack ? to : nil).join 2859 elsif fromhack # via http
2812 elsif fromhack 2860 from = rewritefrom(@params['user'], nick, groupmode ? to : sender)
2813 from = rewritefrom(@params['user'], nick, to)
2814 end 2861 end
2815 header = { 2862 header = {
2816 "Reply-to" => to,
2817 "X-ML-Driver" => ($hgid || @myname), 2863 "X-ML-Driver" => ($hgid || @myname),
2818 "X-ML-Driver-URI" => $myurl, 2864 "X-ML-Driver-URI" => $myurl,
2819 "X-ML-Name" => xmlname, 2865 }
2820 "X-ML-URI" => sprintf("%s?-groupman+%s", @opt['url'], name), 2866 if groupmode
2821 "Return-path" => returnpath} 2867 header["Reply-to"] = to
2868 header["X-ML-Name"] = xmlname
2869 header["X-ML-URI"] = sprintf("%s?-groupman+%s", @opt['url'], name)
2870 header["Return-path"] = returnpath
2871 end
2822 Dir.chdir @mydir 2872 Dir.chdir @mydir
2823 rcpts = if grepgroup(name) 2873 if groupmode
2824 @sc.members(name) 2874 rcpts = if grepgroup(name)
2825 else 2875 @sc.members(name)
2826 [name] 2876 else
2827 end.collect {|u| mailaddress(u, name).split(/,\s*|\s+/)}.flatten 2877 [name]
2878 end.collect {|u| mailaddress(u, name).split(/,\s*|\s+/)}.flatten
2879 else
2880 rcpts = @sc.mailaddress(name).split(/,\s*|\s+/).flatten
2881 rcpts += @sc.mailaddress(user).split(/,\s*|\s+/).flatten # +sender
2882 end
2828 ENV["QMAILINJECT"] = "r" # for ML mode, use verp 2883 ENV["QMAILINJECT"] = "r" # for ML mode, use verp
2829 # 2884 #
2830 # On mail mode, check if sender can send message to list. 2885 # On mail mode, check if sender can send message to list.
2831 if viamail && @sc.getgroupattr(name, 'limitsender') 2886 if viamail && @sc.getgroupattr(name, 'limitsender')
2832 s = ENV['SENDER'] 2887 s = ENV['SENDER']
2845 # 2900 #
2846 # OK to send, go ahead 2901 # OK to send, go ahead
2847 sendMail(to, subj, body, from, rcpts, 2902 sendMail(to, subj, body, from, rcpts,
2848 header, 2903 header,
2849 ENV['SENDER'], 2904 ENV['SENDER'],
2850 @opt['mlspooling'] ? mldir : nil) 2905 spooling ? mldir : nil)
2851 if !viamail then 2906 if !viamail then
2852 @O.print @H.elementln("h1"){msg('sendall_done')} 2907 @O.print @H.elementln("h1"){msg('sendall_done')}
2853 @O.print @H.p(sprintf(msg('sendall_head'), 2908 @O.print @H.p(sprintf(msg('sendall_head'),
2854 nickname(name))+" "+msg('done')) 2909 nickname(name))+" "+msg('done'))
2855 link2home() 2910 link2home()
2866 return true 2921 return true
2867 end 2922 end
2868 2923
2869 user=@params['user'] 2924 user=@params['user']
2870 nickname = @sc.nickname(user) 2925 nickname = @sc.nickname(user)
2926 groupmode = @sc.isgroup(name)
2871 @O.print @H.elementln("h1") { 2927 @O.print @H.elementln("h1") {
2872 @mybase+' '+msg('sendall').sub("<br>", " ") 2928 @mybase+' '+msg('sendall').sub("<br>", " ")
2873 } 2929 }
2874 @O.print @H.elementln("h2") { 2930 @O.print @H.elementln("h2") {
2875 sprintf(msg('sendall_head'), nickname(name)) 2931 sprintf(msg('sendall_head'), nickname(name))
2876 } 2932 }
2877 @O.print @H.p(sprintf("(%s: %s)", msg('member'), 2933 list = groupmode ? @sc.members(name) : [name, user]
2878 @sc.membernames(name).join(", "))) 2934 @O.print @H.p(sprintf("%s: %s", msg('member'),
2935 list.collect {|u|
2936 @H.element("abbr", "title"=>u){
2937 @sc.nickname(u)
2938 }
2939 }.join(",\n")))
2940 @O.print @H.p(sprintf("(total %d)", list.length))+"\n"
2879 @O.print \ 2941 @O.print \
2880 @H.elementln("form", {'action' => @myname+'?-list', 'method'=>"POST"}) { 2942 @H.elementln("form", {'action' => @myname+'?-list', 'method'=>"POST"}) {
2881 @H.elementln("table"){ 2943 @H.elementln("table"){
2882 @H.elementln("tr"){ 2944 @H.elementln("tr"){
2883 @H.element("td"){"Subject"} + \ 2945 @H.element("td"){"Subject"} + \
3109 if memberp # move this user to the beginning of list 3171 if memberp # move this user to the beginning of list
3110 memlist.delete(user) 3172 memlist.delete(user)
3111 memlist.unshift(user) 3173 memlist.unshift(user)
3112 end 3174 end
3113 memlist.collect{|u| 3175 memlist.collect{|u|
3114 (adminp \ 3176 if u == user
3115 ? @H.element("abbr", "title"=>mailaddress(u)) { 3177 @sc.nickname(u) + \
3116 @sc.nickname(u) 3178 "("+@H.text("mail4-#{g}", memberp, 30, 180)+")"
3117 } 3179 else
3118 : @sc.nickname(u)) + \ 3180 @H.a(@myname+"?-listdraft+#{u}",
3119 ((u == user) ? ("("+@H.text("mail4-#{g}", memberp, 30, 180)+")") : "") 3181 @H.element("abbr", "title"=>u) {
3182 @sc.nickname(u)
3183 })
3184 end
3120 }.join(", ") 3185 }.join(", ")
3121 } 3186 }
3122 } + \ 3187 } + \
3123 @H.element("td"){ 3188 @H.element("td"){
3124 @H.a(@myname+"?-listdraft+#{g}", msg('sendall')) 3189 @H.a(@myname+"?-listdraft+#{g}", msg('sendall'))
3125 } 3190 }
3126 } 3191 }
3127 }.join("\n") 3192 }.join("\n")
3128 } + \ 3193 } + \
3129 '' + \ 3194 '' + \
3195 @H.p(msg('address2send')) + \
3130 @H.p(msg('groupwarn', 'shortnameplz')) + \ 3196 @H.p(msg('groupwarn', 'shortnameplz')) + \
3131 @H.submit_reset("GO") 3197 @H.submit_reset("GO")
3132 } # form 3198 } # form
3133 end 3199 end
3134 def groupnamesString() 3200 def groupnamesString()
3365 end.join + \ 3431 end.join + \
3366 (group ? @H.elementln("p") { 3432 (group ? @H.elementln("p") {
3367 sprintf(msg('mladdress'), defaultmladdress(group)) + \ 3433 sprintf(msg('mladdress'), defaultmladdress(group)) + \
3368 @H.text("mladdress", @sc.getgroupattr(group, 'mladdress'), 3434 @H.text("mladdress", @sc.getgroupattr(group, 'mladdress'),
3369 @opt['size'], 80) 3435 @opt['size'], 80)
3436 } + \
3437 @H.elementln('p') {
3438 sprintf(msg('xmlname'), group) + \
3439 @H.text("xmlname", @sc.getgroupattr(group, 'xmlname'),
3440 @opt['size'], 80)
3370 } : "") + \ 3441 } : "") + \
3371 @H.elementln('p') {
3372 sprintf(msg('xmlname'), group) + \
3373 @H.text("xmlname", @sc.getgroupattr(group, 'xmlname'),
3374 @opt['size'], 80)
3375 } + \
3376 @H.elementln('p') { 3442 @H.elementln('p') {
3377 n = -1 3443 n = -1
3378 curtag = @sc.getgroupattr(group, 'subjtag') 3444 curtag = @sc.getgroupattr(group, 'subjtag')
3379 values = @subjtags.collect {|x| 3445 values = @subjtags.collect {|x|
3380 sprintf(' <option value="%d"%s>%s</option>', n+=1, 3446 sprintf(' <option value="%d"%s>%s</option>', n+=1,
3994 @job = 'list' # + exit 4060 @job = 'list' # + exit
3995 when "-newgroup" 4061 when "-newgroup"
3996 @job = 'newgroup' 4062 @job = 'newgroup'
3997 when /^-(admgroup|listdraft)$/ 4063 when /^-(admgroup|listdraft)$/
3998 ARGV.shift 4064 ARGV.shift
3999 gr = safecopy(grepgroup(ARGV[0])) 4065 #gr = safecopy(grepgroup(ARGV[0]))
4066 gr = safecopy(ARGV[0]) # -listdraft can be called with user 2013/12/12
4000 ##gr.untaint 4067 ##gr.untaint
4001 @job = safecopy($1)+' "'+gr+'"' 4068 @job = safecopy($1)+' "'+gr+'"'
4002 when "-admgroupsub" 4069 when "-admgroupsub"
4003 @job = 'admgroupsub' 4070 @job = 'admgroupsub'
4004 when "-newgroupsub" 4071 when "-newgroupsub"

yatex.org