├── pamphlets ├── CentOS.odt ├── CentOS.pdf ├── CentOS-SIGs.odt ├── CentOS-SIGs.pdf ├── CentOS-ContainerPipeline.odt └── CentOS-ContainerPipeline.pdf ├── scripts ├── README ├── who.sh ├── tshirts.py ├── followers.py ├── get_blogs.pl ├── meeting_minutes.pl ├── centos-announcements.pl ├── rss_updates.py ├── sig_reporting │ └── reporting.pl └── get_meetups ├── README.md └── LICENSE /pamphlets/CentOS.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS.odt -------------------------------------------------------------------------------- /pamphlets/CentOS.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS.pdf -------------------------------------------------------------------------------- /pamphlets/CentOS-SIGs.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-SIGs.odt -------------------------------------------------------------------------------- /pamphlets/CentOS-SIGs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-SIGs.pdf -------------------------------------------------------------------------------- /pamphlets/CentOS-ContainerPipeline.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-ContainerPipeline.odt -------------------------------------------------------------------------------- /pamphlets/CentOS-ContainerPipeline.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbowen/centos-community-tools/HEAD/pamphlets/CentOS-ContainerPipeline.pdf -------------------------------------------------------------------------------- /scripts/README: -------------------------------------------------------------------------------- 1 | Various scripts that are used around the CentOS community. 2 | 3 | Stuff you might need to install: 4 | 5 | dnf install python3-feedparser 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # centos-community-tools 2 | CentOS Community Manager tools and scripts 3 | 4 | Automates some of the mudane daily tasks that are part of my job as CentOS Community Manager. 5 | -------------------------------------------------------------------------------- /scripts/who.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Who contributed (post-process output of rss_updates.py (Numbers are 3 | # not reliable, as one person will invariably appear more than once in a 4 | # given change, but it's a good way to get a list to start with.) 5 | 6 | egrep '@' rss_updates.html | egrep ' GMT ' | sed 's/^.* GMT - //;' | sed 's/\>.*$//' | sort | uniq -c | sort -rn > who_contributed.txt 7 | 8 | -------------------------------------------------------------------------------- /scripts/tshirts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | 4 | # How many tshirts to order? This uses a bell curve distribution of 5 | # sizes, which works fairly well in Europe. Might want to skew larger 6 | # for US events. 7 | 8 | shirts = int(input("How many shirts? ")) 9 | 10 | print ("For a total of " + str(shirts) + " shirts, order:\n" ) 11 | 12 | s = shirts * 5 / 50 13 | m = shirts * 12 / 50 14 | l = shirts * 16 / 50 15 | xl = shirts * 12 / 50 16 | xxl = shirts * 5 / 50 17 | 18 | # For APAC ... 19 | # s = shirts * 7 / 50 20 | # m = shirts * 17 / 50 21 | # l = shirts * 16 / 50 22 | # xl = shirts * 7 / 50 23 | # xxl = shirts * 3 / 50 24 | 25 | print( str(s) + " small") 26 | print( str(m) + " medium") 27 | print( str(l) + " large") 28 | print( str(xl) + " xl") 29 | print( str(xxl) + " 2xl") 30 | 31 | -------------------------------------------------------------------------------- /scripts/followers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # How many followers do you have? 4 | import urllib.request 5 | import re 6 | 7 | print ("This doesn't work any more because Twitter is actively preventing it. Sorry.") 8 | quit() 9 | 10 | feeds = [ 11 | 'rbowen','centosproject','centos' 12 | ]; 13 | for feed in feeds: 14 | req = urllib.request.Request( 'https://twitter.com/' + feed + '/', 15 | data = None, 16 | headers={ 17 | 'User-Agent': 18 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)', 19 | } ) 20 | f = urllib.request.urlopen(req) 21 | html = f.read().decode('utf-8') 22 | 23 | # Looks like ... 24 | #
2,615
25 | #
Followers
26 | 27 | print ( feed + ': ' + re.search('.*?followers">.+?statnum">([\d,MK]+).*?<.*?statlabel"> Followers.*', html, re.DOTALL).group(1) ) 28 | 29 | -------------------------------------------------------------------------------- /scripts/get_blogs.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | 5 | use XML::Feed; 6 | use URI; 7 | use XML::Simple; 8 | use LWP::Simple; 9 | 10 | $|++; 11 | 12 | my $url = URI->new("http://planet.centos.org/atom.xml"); 13 | 14 | my $feed = XML::Feed->parse($url); 15 | 16 | open (my $md, '>', 'blogs.md'); 17 | open (my $tweets, '>', 'blogs.tweets.csv'); 18 | 19 | foreach ( $feed->entries ) { 20 | 21 | print $md "**" 22 | . $_->title . "** by " 23 | . $_->author . "\n\n"; 24 | my $body = $_->content->body; 25 | 26 | # Or possibly the summary? 27 | unless ( $body ) { 28 | $body = $_->summary->body; 29 | $body =~ s/\n.*//is 30 | } 31 | 32 | $body =~ s/^.*?]*?>//i; 33 | $body =~ s!

.*$!!is; 34 | $body =~ s/<[^>]+>//igs; # Strip HTML 35 | $body =~ s/[\r\n]/ /gs; # Strip newlines from whatever's left 36 | print $md "> $body\n\n"; 37 | 38 | my $link = $_->link; 39 | print $md "Read more at [$link]($link)\n\n\n"; 40 | 41 | print $tweets '"01/01/2018 00:00:00","' . $_->title 42 | . ' #OpenStack #RDOCommunity","' 43 | . $link . '"' . "\n"; 44 | } 45 | 46 | close $md; 47 | close $tweets; 48 | 49 | print "\nDone\n"; 50 | -------------------------------------------------------------------------------- /scripts/meeting_minutes.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use DateTime; 5 | 6 | use LWP::Simple qw(get); 7 | use Getopt::Long qw(GetOptions); 8 | my $help=0; 9 | GetOptions("help" => \$help) or help(); 10 | help() if $help; 11 | 12 | my $dt = DateTime->now( time_zone => 'local' ); 13 | my $year = $ARGV[0] || $dt->year(); 14 | my $month = $ARGV[1] || $dt->month_name(); 15 | 16 | my $url = 'https://centos.org/minutes/' . $year . '/' . $month . '/'; 17 | # print "*** $url ***\n"; 18 | my $html = get $url; 19 | 20 | if ($html) { 21 | my @html=split(/\n/, $html); 22 | foreach my $line(@html) { 23 | 24 | # Looking for meeting minutes like centos-meeting.2018-09-04-13.01.html 25 | next unless $line =~ m/(centos-meeting|epel)(\d?)\.$year-\d\d-\d\d-\d\d\.\d\d\.html/; 26 | 27 | $line =~ s/^.+href="//; 28 | 29 | my ($filename, $date) = ( $line =~ m/^(.+?)".+?"indexcollastmod">(.+?) / ); 30 | 31 | # On $date, there are meeting minutes in $filename. What SIG was that? 32 | my $minutesurl = $url . "$filename"; 33 | my $minutes = get $minutesurl; 34 | 35 | # Get the meeting title from the minutes. 36 | my ($title) = ( $minutes =~ m!

#(?:centos-meeting\d?|epel): (.*?)

! ); 37 | 38 | print "Meeting minutes - $title, $date - $minutesurl #CentOS #SIG\n"; 39 | 40 | } 41 | } else { 42 | print "There appear to be no meeting minutes for that month. See $url to confirm.\n"; 43 | } 44 | 45 | sub help { 46 | print "Usage: ./$0 YEAR MONTH - eg, ./$0 2018 September\n"; 47 | print "Defaults to current month of none specified.\n"; 48 | exit(); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /scripts/centos-announcements.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use DateTime; 5 | 6 | use LWP::Simple qw(get); 7 | 8 | use Getopt::Long qw(GetOptions); 9 | my $help=0; 10 | GetOptions("help" => \$help) or help(); 11 | help() if $help; 12 | 13 | # Start scheduling them today 14 | my $dt = DateTime->now( time_zone => 'local' ); 15 | my $mday = $dt->day(); 16 | my $start_mday = $mday; 17 | my $mon = $dt->month(); 18 | 19 | # Which month are we reporting on? 20 | my $year = $ARGV[0] || $dt->year(); 21 | my $month = $ARGV[1] || $dt->month_name(); 22 | 23 | my $baseurl = 'https://lists.centos.org/pipermail/centos-announce/' . $year . '-' . $month; 24 | my $url = $baseurl . '/thread.html'; 25 | # print "*** $url *** \n"; 26 | my $html = get $url; 27 | 28 | unless ($html) { 29 | print "It appears that there haven't been any announcements yet in the specified month.\n"; 30 | exit(); 31 | } 32 | 33 | open (my $tweets, '>', 'announce_tweets.csv' ); 34 | open (my $news, '>', 'announce_newsletter.txt' ); 35 | my (@ceea, @cesa, @ceba, @other); 36 | 37 | my @html=split(/\n/, $html); 38 | foreach my $line (@html) { 39 | 40 | # Looking for lines like 41 | #
  • [CentOS-announce] CEEA-2018:2675 CentOS 6 microcode_ctl ... 42 | # print "Considering $line\n"; 43 | next unless $line =~ m/^ 30; 64 | 65 | my $announce = "
  • $date: $subject - $baseurl/$filename
  • "; 66 | 67 | push (@cesa, $announce) if $subject =~ m/CESA/; 68 | push (@ceea, $announce) if $subject =~ m/CEEA/; 69 | push (@ceba, $announce) if $subject =~ m/CEBA/; 70 | push (@other, $announce) unless $subject =~ m/CE.A/; 71 | } 72 | 73 | errata( $month, $news, 'Enhancements', 'CEEA', @ceea ); 74 | errata( $month, $news, 'Security', 'CESA', @cesa ); 75 | errata( $month, $news, 'Bugfix', 'CEBA', @ceba ); 76 | errata( $month, $news, 'Other', 'Other', @other); 77 | 78 | sub errata { 79 | my ( $month, $news, $name, $acro, @arry ) = @_; 80 | 81 | if ($name eq 'Other') { 82 | print $news qq~\n\n

    Other releases

    83 | 84 | The following releases also happened during $month: 85 | 86 | ~; 87 | } else { 88 | print $news qq~\n\n

    Errata and $name Advisories

    89 | 90 | We issued the following $acro (CentOS Errata and $name Advisories) during $month: 91 | 92 | ~; 93 | } 94 | 95 | print $news "\n\n"; 102 | 103 | } 104 | 105 | sub help { 106 | print "Usage: $0 YEAR MONTH - eg, $0 2018 September\n"; 107 | print "Defaults to current month if none specified.\n"; 108 | exit(); 109 | } 110 | 111 | -------------------------------------------------------------------------------- /scripts/rss_updates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | import urllib3 4 | import sqlite3 5 | import feedparser 6 | import re 7 | 8 | db_connection = sqlite3.connect('centos_update_rss.sqlite') 9 | db = db_connection.cursor() 10 | 11 | # Uncomment this line to start with a fresh database 12 | db.execute('DROP TABLE centosupdates') 13 | 14 | db.execute('CREATE TABLE IF NOT EXISTS centosupdates (title TEXT, id TEXT)') 15 | out = open('rss_updates.txt', 'w') 16 | html = open('rss_updates.html', 'w') 17 | 18 | def read_release_feed(): 19 | """ Get packages from RSS feed """ 20 | feedbase = 'https://feeds.centos.org/'; 21 | feeds = [ 22 | 23 | # Stream 8 24 | 'centos-8-stream-aarch64-BaseOS', 25 | 'centos-8-stream-aarch64-AppStream', 26 | 'centos-8-stream-aarch64-PowerTools', 27 | 'centos-8-stream-ppc64le-AppStream', 28 | 'centos-8-stream-ppc64le-BaseOS', 29 | 'centos-8-stream-ppc64le-PowerTools', 30 | 'centos-8-stream-x86_64-AppStream', 31 | 'centos-8-stream-x86_64-BaseOS', 32 | 'centos-8-stream-x86_64-PowerTools', 33 | 34 | # Stream 9 35 | 'centos-9-stream-aarch64-AppStream', 36 | 'centos-9-stream-aarch64-BaseOS', 37 | 'centos-9-stream-aarch64-CRB', 38 | 'centos-9-stream-ppc64le-AppStream', 39 | 'centos-9-stream-ppc64le-BaseOS', 40 | 'centos-9-stream-ppc64le-CRB', 41 | 'centos-9-stream-s390x-AppStream', 42 | 'centos-9-stream-s390x-BaseOS', 43 | 'centos-9-stream-s390x-CRB', 44 | 'centos-9-stream-x86_64-AppStream', 45 | 'centos-9-stream-x86_64-BaseOS', 46 | 'centos-9-stream-x86_64-CRB' 47 | 48 | ] 49 | 50 | # HTML headers 51 | html.write("\n"); 55 | 56 | for f in feeds: 57 | count = 0 58 | feed = feedparser.parse(feedbase + f + '.xml') 59 | print("Checking new packages in " + f + " ", end="\r") 60 | out.write("\n\nNew packages in " + f + ":") 61 | html.write("

    New packages in " + f + "

    ") 62 | 63 | for release in feed['entries']: 64 | if release_is_not_db(release['title'], release['id']): 65 | format_release(release) 66 | add_release_to_db(release['title'], release['id']) 67 | count += 1 68 | 69 | if (count == 0): 70 | msg = "No new packages in " + f + " since last scan." 71 | out.write("\n" + msg + "\n\n") 72 | html.write("

    " + msg + "

    \n") 73 | 74 | print("See rss_updates.txt and rss_updates.html for results \n") 75 | 76 | def release_is_not_db(release_title, release_id): 77 | """ Check if we've already seen a release 78 | Args: 79 | release_title (str): Title 80 | release_id (str): Unique ID of release 81 | Return: 82 | True if we haven't seen it yet 83 | False otherwise 84 | """ 85 | db.execute("SELECT * from centosupdates WHERE title=? AND id=?", (release_title, release_id)) 86 | if not db.fetchall(): 87 | return True 88 | else: 89 | return False 90 | 91 | 92 | def add_release_to_db(release_title, release_id): 93 | """ Add a new release to the database 94 | Args: 95 | release_title (str): The title of a release 96 | release_id (str): Unique ID of release 97 | """ 98 | 99 | db.execute("INSERT INTO centosupdates VALUES (?, ?)", (release_title, release_id)) 100 | db_connection.commit() 101 | 102 | 103 | def format_release( release ): 104 | """ Prettyprint the release info """ 105 | out.write("\n" + release['published'] + ": " + release['title']) 106 | html.write("\n

    " + release['published'] + ": " + release['title'] + "

    ") 107 | 108 | """ Strip HTML, special chars """ 109 | summary = re.sub('<[^<]+?>', '', release['summary']) 110 | summary = re.sub('<', '<', summary) 111 | summary = re.sub('>', '>', summary) 112 | 113 | """ Drop everything past the first (most recent) Change Log: entry """ 114 | summary = re.sub('(Change Log:\n\n.*?)\n\n.*$', r'\1', summary, flags=re.S) 115 | 116 | html.write(release['summary']) 117 | html.write("\n\n") 118 | print(".", end ="", flush=True) 119 | 120 | if __name__ == '__main__': 121 | 122 | read_release_feed() 123 | db_connection.close() 124 | out.close() 125 | html.close() 126 | 127 | -------------------------------------------------------------------------------- /scripts/sig_reporting/reporting.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use DateTime; 5 | use Getopt::Long qw(GetOptions); 6 | use Term::ANSIColor; 7 | 8 | my $help = 0; 9 | my $t = 0; 10 | GetOptions( 11 | "help" => \$help, 12 | "this" => \$t, # Defaults to NEXT month 13 | ); 14 | 15 | help() if $help; 16 | 17 | my %reporters = ( 18 | 1 => [ 19 | [ 'Core', 'sig-core@centosproject.org' ], 20 | [ 'Config Management', 'sig-configmanagement@centosproject.org' ], 21 | [ 'Software Collections', 'sig-sclo@centosproject.org' ], 22 | [ 'Hyperscale', 'sig-hyperscale@centosproject.org' ], 23 | [ 'Stream Feature Request', 'ENOSIG' ], 24 | [ 'kmods', 'sig-kmods@centosproject.org' ], 25 | [ 'Automotive', 'jefro@redhat.com' ], 26 | ], 27 | 28 | 2 => [ 29 | [ 'Alt Arch', 'sig-altarch@centosproject.org' ], 30 | [ 'Cloud', 'sig-cloud@centosproject.org' ], 31 | [ 'NFV', 'sig-nfv@centosproject.org' ], 32 | [ 'Promo', 'centos-promo@centos.org' ], 33 | [ 'Storage', 'sig-storage@centosproject.org' ], 34 | [ 'Messaging', 'sig-messaging@centosproject.org' ], 35 | ], 36 | 37 | 3 => [ 38 | [ 'Artwork', 'sig-artwork@centosproject.org' ], 39 | [ 'Cloud Instance', 'sig-cloudinstance@centosproject.org' ], 40 | [ 'OpsTools', 'sig-opstools@centosproject.org' ], 41 | [ 'Public CI', 'ENOSIG' ], 42 | [ 'Virtualization', 'sig-virt@centosproject.org' ], 43 | [ 'Infrastructure', 'sig-infra@centosproject.org' ], 44 | ] 45 | 46 | ); 47 | 48 | my @sigs; 49 | 50 | # Who is reporting next month? 51 | # Weirdly, just doing month+1 can sometimes skip past a shorter month. 52 | # eg, March 31 + 1 month puts you in May, not April. 53 | my $today = DateTime->now( time_zone => 'local' ); 54 | my $dt = DateTime->new( 55 | day => 1, 56 | month => $today->month(), 57 | year => $today->year() 58 | )->add( months => ( $t ? 0 : 1 ) ); # Defaults to next month 59 | 60 | my $month = $dt->month_name; 61 | print "Reports due for $month.\n"; 62 | 63 | my $group = ( ( $dt->month() % 3 ) || 3 ); 64 | 65 | print "We expect reports from SIGs in Group " . $group . ":\n\n"; 66 | 67 | foreach my $sig ( @{ $reporters{$group} } ) { 68 | print " * $sig->[0]\n"; 69 | push @sigs, $sig->[0]; 70 | } 71 | 72 | # When is the first Monday? 73 | my $dow = $dt->day_of_week; # 1-7 (Monday is 1) 74 | my $first_monday = ( $dow == 1 ) ? 1 : (9 - $dow); 75 | 76 | print "\n"; 77 | 78 | print "Send the following reminders to these SIGs\n"; 79 | 80 | foreach my $sig ( @{ $reporters{$group} } ) { 81 | my $name = $sig->[0]; 82 | my $email = $sig->[1]; 83 | 84 | # TODO: Calculate the first Monday of the month, rather than editing 85 | # this every time. 86 | 87 | print qq~ 88 | -------------------------------------------------- 89 | 90 | SUBJECT: CentOS $name SIG quarterly report for $month newsletter 91 | To: $email 92 | 93 | Dear $name SIG, 94 | 95 | As documented in the wiki, we request that each SIG reports quarterly 96 | about the accomplishments of their SIG during the previous quarter. 97 | 98 | (You are receiving this because you are listed in the $name 99 | SIG ACL on accounts.centos.org. If that is not accurate, you should take 100 | it up with your SIG lead. See https://accounts.centos.org/groups/) 101 | 102 | As you may be aware, your SIG - the $name SIG - is on the 103 | list for $month reports. See "Reporting" section towards the bottom of 104 | https://wiki.centos.org/SpecialInterestGroup 105 | 106 | I encourage you to have a look at the report produced by the Cloud SIG 107 | as an example of what we're looking for. It's not onerous - just an 108 | overview of releases and community activity over the preceding few 109 | months. https://blog.centos.org/2019/01/centos-cloud-sig-quarterly-report/ 110 | 111 | Reports can be submitted either directly to me, or, if you prefer, you 112 | can submit it as a blog post on blog.centos.org. 113 | 114 | See https://wiki.centos.org/SpecialInterestGroup/Promo/Blog for details 115 | about posting to the CentOS blog. 116 | 117 | The report will be included in the $month community newsletter. As 118 | such I need it by Monday $month $first_monday, at the very latest. 119 | 120 | Thanks! 121 | 122 | ~; 123 | 124 | } 125 | 126 | print "Summary: reports expected for $month:\n"; 127 | foreach my $sig (@sigs) { 128 | print " * $sig\n"; 129 | } 130 | 131 | # Usage reminder 132 | unless ($t) { 133 | print color("bold red"); 134 | print "\nNote: Use -t to run for THIS month. Defaults to NEXT month.\n\n"; 135 | print color('reset'); 136 | } 137 | 138 | sub help { 139 | print "\nNote: Use -t to run for THIS month. Defaults to NEXT month.\n"; 140 | print "Usage: $0\n"; 141 | print " use -t to run for THIS month\n"; 142 | print "Defaults to NEXT month if run without -t.\n"; 143 | exit(); 144 | } 145 | 146 | -------------------------------------------------------------------------------- /scripts/get_meetups: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import datetime 3 | from datetime import date 4 | import urllib.request 5 | import json 6 | import time 7 | import re 8 | 9 | # Magic 10 | import sys # sys.setdefaultencoding is cancelled by site.py 11 | # reload(sys) # to re-enable sys.setdefaultencoding() 12 | # sys.setdefaultencoding('utf-8') 13 | 14 | print ("Rounding up the ponies ...") 15 | 16 | # NOTE: This is Rich's personal API key. Get your own. 17 | key = "3a7711454d145e404e531c2ee6f391d" 18 | 19 | keyword = sys.argv[1] if len(sys.argv) >= 2 else 'linux' 20 | url = "https://api.meetup.com/2/open_events?&sign=true&photo-host=public&state=ky&city=lexington&country=usa&text=" + keyword + "&radius=10000&sign=true&key=" + key 21 | 22 | print(url) 23 | week = 7 24 | twoweek = 14 25 | now = datetime.datetime.now() 26 | nowts = time.mktime(now.timetuple()) 27 | 28 | print ("Fetching meetups ...") 29 | 30 | response = urllib.request.urlopen(url) 31 | m = response.read().decode('utf-8') 32 | 33 | print ("Parsing results ...") 34 | r = json.loads(m) 35 | meetups = r['results'] 36 | 37 | groups = {} 38 | 39 | print ("Fetching group details ...") 40 | 41 | for meetup in meetups: 42 | groups[ str( meetup['group']['id'] ) ] = meetup['group']['name'] 43 | 44 | keys = groups.keys() 45 | keyarg = ",".join( keys ) 46 | 47 | group_url = "https://api.meetup.com/2/groups?&sign=true&photo-host=public&group_id=" + keyarg + "&key=" + key 48 | 49 | response = urllib2.urlopen( group_url ) 50 | m = response.read() 51 | r = json.loads(m) 52 | 53 | grps = r['results'] 54 | 55 | grp_deets = {} 56 | for g in grps: 57 | grp_deets[ g['id'] ] = g 58 | 59 | seen = set() 60 | 61 | print ("Ok, ready to print meetup details ...") 62 | 63 | weeklater = (nowts * 1000 ) + ( week * 86400 * 1000 ) 64 | twoweeklater = (nowts * 1000 ) + ( twoweek * 86400 * 1000 ) 65 | 66 | tweets = open('meetups.tweets.csv', 'w') 67 | mlist = open('meetups.mlist', 'w') 68 | # wiki = open('meetups.wiki', 'w') 69 | # md = open('meetups.md', 'w') 70 | yml = open('meetups.yml', 'w') 71 | 72 | # Standard intro to mailing list post 73 | mlist.write( '''The following are the meetups I'm aware of in the next two weeks where 74 | CentOS and/or Linux enthusiasts are likely to be present. 75 | 76 | If there's a meetup in your area, please consider attending. If you 77 | attend, please consider taking a few photos, and possibly even writing 78 | up a brief summary of what was covered. 79 | 80 | --Rich 81 | 82 | ''') 83 | 84 | yml.write( '''name: RDO Meetups 85 | type: series 86 | tags: RDO 87 | 88 | talks: 89 | 90 | ''') 91 | 92 | for meetup in meetups: 93 | eventts = int( meetup['time'] + meetup['utc_offset'] ) 94 | eventutc = meetup['time'] 95 | 96 | # Skip it if it's more than two weeks away 97 | if eventts > twoweeklater: 98 | continue 99 | 100 | eventtimeutc = datetime.datetime.fromtimestamp(eventutc/1000) 101 | endtimeutc = datetime.datetime.fromtimestamp(eventutc/1000 + 3600) 102 | eventtime = date.fromtimestamp( eventts/1000 ) 103 | t = eventtime.strftime("%A %B %d") 104 | 105 | # Skip it if we've already seen it 106 | uniq = meetup['name'] + t 107 | if uniq in seen: 108 | continue 109 | seen.add( uniq ) 110 | 111 | 112 | # Group information ... 113 | grp = grp_deets[ meetup['group']['id'] ] 114 | 115 | # Skip it if it doesn't actually mention the keyword 116 | if (re.search(keyword, grp['name'], re.IGNORECASE) == None) \ 117 | and (re.search(keyword, meetup['name'], re.IGNORECASE) == None) \ 118 | and (re.search(keyword, meetup['event_url'], re.IGNORECASE) == None): 119 | continue 120 | 121 | # For yml 122 | mname = meetup['name'] 123 | mname = mname.replace(':','') 124 | eventout = '- title: ' + mname + "\n" 125 | eventout = eventout + ' location: ' + grp['city'] + ', ' 126 | if 'state' in grp.keys(): 127 | eventout = eventout + grp['state'] + "\n" 128 | else: 129 | eventout = eventout + grp['country'] + "\n" 130 | eventout = eventout + ' speaker: ' + grp['name'] + "\n" 131 | eventout = eventout + ' start: ' + eventtimeutc.strftime("%Y-%m-%d %H:%M") + ' UTC' + "\n" 132 | eventout = eventout + ' end: ' + endtimeutc.strftime("%Y-%m-%d %H:%M") + ' UTC' + "\n" 133 | eventout = eventout + ' description: |' + "\n" 134 | eventout = eventout + ' ' + grp['city'] + ', ' 135 | if 'state' in grp.keys(): 136 | eventout = eventout + grp['state'] + ', ' 137 | eventout = eventout + grp['country'] + "\n" 138 | eventout = eventout + ' ' + mname + "\n" 139 | eventout = eventout + ' ' + meetup['event_url'] + "\n" 140 | 141 | eventout = eventout + "\n" 142 | yml.write( str(eventout) ) 143 | 144 | # For mailing list 145 | eventout = '* ' + t + ' in ' + grp['city'] + ', ' 146 | if 'state' in grp.keys(): 147 | eventout = eventout + grp['state'] + ', ' 148 | eventout = eventout + grp['country'] + ': ' + meetup['name'] + ' - ' + meetup['event_url'] + "\n\n" 149 | mlist.write( str( eventout )) 150 | 151 | # For Twitter, a week is enough 152 | if eventts > weeklater: 153 | continue 154 | 155 | # For Twitter 156 | eventout = '"01/01/2018 00:00:00","' + t + ' in ' + grp['city'] + ', ' 157 | if 'state' in grp.keys(): 158 | eventout = eventout + grp['state'] + ', ' 159 | eventout = eventout + grp['country'] + ': ' + meetup['name'] + '","' + meetup['event_url'] + '"' + "\n" 160 | tweets.write( str( eventout )) 161 | 162 | # Barn door 163 | tweets.close() 164 | mlist.close() 165 | yml.close() 166 | 167 | print ("Done!\n") 168 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------